graphics: preserve stencil view layers through uploads and clears

This commit is contained in:
nmzik
2026-10-04 02:26:19 +04:00
parent d890bb190c
commit 068d7621db
4 changed files with 142 additions and 11 deletions
+42 -10
View File
@@ -1037,6 +1037,8 @@ void TextureCache::UploadImage(Image& image, Buffer& source, uint64_t source_off
auto info = destination.info;
if (image.depth_id) {
info.data = image.info.data;
info.resources = {image.stencil_subresources.level_count,
image.stencil_subresources.layer_count};
info.guest_format = Prospero::BufferFormat::k8UInt;
info.bytes_per_block = 1;
if (info.IsTiled()) info.pitch = TileGetDepthPitch(info.extent.width, 1, 0);
@@ -1051,6 +1053,11 @@ void TextureCache::UploadImage(Image& image, Buffer& source, uint64_t source_off
auto copies = BuildDepthCopies(info, full_slice_size, image.depth_id
? vk::ImageAspectFlagBits::eStencil
: vk::ImageAspectFlagBits::eDepth);
if (image.depth_id) {
for (auto& copy: copies) {
copy.imageSubresource.baseArrayLayer += image.stencil_subresources.base_layer;
}
}
TileManager::Result linear {source.Handle(), source_offset, source.Size() - source_offset};
if (info.IsTiled()) {
const auto tiles = BuildDepthTiles(info);
@@ -1239,7 +1246,8 @@ ImageId TextureCache::AssociateStencil(ImageId depth_id, GuestRange stencil) {
}
auto& record = m_slot_images[association];
TouchImage(record);
record.depth_id = depth_id;
record.depth_id = depth_id;
record.stencil_subresources = depth.stencil_subresources;
return association;
}
@@ -1445,6 +1453,18 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
TouchImage(image);
image.MarkGpuModified();
image.usage.depth_target = true;
if (desc.info.HasStencil()) {
const auto layers = image.info.resources.layers;
EXIT_IF(layers == 0 || image.info.data.size % layers != 0 ||
desc.info.data.address < image.info.data.address);
const auto slice_size = image.info.data.size / layers;
const auto offset = desc.info.data.address - image.info.data.address;
EXIT_IF(slice_size == 0 || offset % slice_size != 0 || offset / slice_size >= layers ||
desc.info.resources.layers > layers - offset / slice_size);
image.stencil_subresources = {0, desc.info.resources.levels,
static_cast<uint32_t>(offset / slice_size),
desc.info.resources.layers};
}
image.info.stencil = desc.info.stencil;
image.info.metadata = desc.info.metadata;
if (desc.info.HasMetadata()) {
@@ -1491,9 +1511,11 @@ bool TextureCache::ClearImageFromBuffer(CommandBuffer& command, uint64_t address
if (command.IsInvalid() || !GuestRange {address, size}.Valid()) {
EXIT("TextureCache: invalid image clear\n");
}
std::scoped_lock lock {m_lock};
ImageId selected {};
vk::ImageAspectFlags aspect {};
std::scoped_lock lock {m_lock};
ImageId selected {};
ImageId stencil_id {};
vk::ImageAspectFlags aspect {};
ImageSubresourceRange subresources;
for (const auto id: FindImagesInRegion(address, size, false)) {
auto owner = m_slot_images.try_get(id);
if (owner == nullptr) {
@@ -1501,10 +1523,13 @@ bool TextureCache::ClearImageFromBuffer(CommandBuffer& command, uint64_t address
}
vk::ImageAspectFlags candidate {};
ImageId candidate_id = id;
ImageSubresourceRange candidate_subresources {
0, owner->info.resources.levels, 0, owner->info.TransferLayers()};
if (owner->depth_id && owner->info.data.address == address &&
owner->info.data.size == size) {
candidate = vk::ImageAspectFlagBits::eStencil;
candidate_id = owner->depth_id;
candidate = vk::ImageAspectFlagBits::eStencil;
candidate_id = owner->depth_id;
candidate_subresources = owner->stencil_subresources;
owner = m_slot_images.try_get(candidate_id);
if (owner == nullptr || owner->backing.image == nullptr || !owner->info.HasStencil()) {
continue;
@@ -1517,11 +1542,13 @@ bool TextureCache::ClearImageFromBuffer(CommandBuffer& command, uint64_t address
if (!candidate) {
continue;
}
if (selected && selected != candidate_id) {
if (selected && (selected != candidate_id || subresources != candidate_subresources)) {
return false;
}
selected = candidate_id;
aspect = candidate;
selected = candidate_id;
stencil_id = candidate == vk::ImageAspectFlagBits::eStencil ? id : ImageId {};
aspect = candidate;
subresources = candidate_subresources;
}
if (!selected) {
return false;
@@ -1543,7 +1570,12 @@ bool TextureCache::ClearImageFromBuffer(CommandBuffer& command, uint64_t address
clear.depthStencil.stencil = stencil_clear;
}
ClearImage(command, selected, image.backing.format,
{aspect, 0, image.info.resources.levels, 0, image.info.TransferLayers()}, clear);
{aspect, subresources.base_level, subresources.level_count,
subresources.base_layer, subresources.layer_count}, clear);
if (stencil_id) {
TrackImage(stencil_id);
CommitGpuWrite(m_slot_images[stencil_id]);
}
return true;
}
@@ -673,7 +673,9 @@ Prospero::BufferFormat RenderTargetTransferFormat(uint32_t bytes_per_element) {
} // namespace ImageOps
Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageInfo& image_info)
: info(image_info), m_graphics(graphics), m_scheduler(scheduler) {
: info(image_info),
stencil_subresources {0, image_info.resources.levels, 0, image_info.resources.layers},
m_graphics(graphics), m_scheduler(scheduler) {
KYTY_PROFILER_FUNCTION();
ImageOps::Validate(info);
m_cpu_dirty =
@@ -148,6 +148,8 @@ public:
uint64_t track_addr = 0;
uint64_t track_addr_end = 0;
ImageId depth_id {};
// The current stencil plane's mapping into the depth image, also retained by its association.
ImageSubresourceRange stencil_subresources;
uint64_t tick_accessed_last = 0;
size_t lru_id = 0;
+95
View File
@@ -6007,6 +6007,101 @@ public:
"sampling");
DestroyBuffer(&layered_raw_d16_readback);
auto layered_stencil_desc = MakeLinearDesc(
base + 0x27d4000, 6 * 4 * sizeof(float),
vk::Format::eD32SfloatS8Uint, Prospero::BufferFormat::k32Float,
Prospero::ImageType::kColor2D, {4, 1, 1}, 6, 4, 1);
layered_stencil_desc.type = BindingType::DepthTarget;
// Four live stencil bytes per slice, with allocation padding.
constexpr uint32_t stencil_slice_size = 16;
layered_stencil_desc.info.stencil = {base + 0x27d6000, 6 * stencil_slice_size};
layered_stencil_desc.view_info.aspect =
vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil;
layered_stencil_desc.view_info.usage =
vk::ImageUsageFlagBits::eDepthStencilAttachment;
const auto layered_stencil_id = texture_cache.FindImage(layered_stencil_desc);
auto &layered_stencil_native = texture_cache.GetImage(layered_stencil_id);
layered_stencil_native.Transit(vk::ImageLayout::eTransferDstOptimal,
vk::AccessFlagBits2::eTransferWrite, {}, scheduler.Current().Handle());
const vk::ClearDepthStencilValue layered_stencil_clear{0.75f, 0x91};
const vk::ImageSubresourceRange layered_stencil_range{
layered_stencil_desc.view_info.aspect, 0, 1, 0, 6};
scheduler.Current().Handle().clearDepthStencilImage(
layered_stencil_native.backing.image,
vk::ImageLayout::eTransferDstOptimal, &layered_stencil_clear, 1,
&layered_stencil_range);
texture_cache.MarkGpuWritten(layered_stencil_id);
std::array<uint32_t, 6> stencil_expected;
stencil_expected.fill(0x91919191u);
constexpr std::array<std::array<uint32_t, 3>, 4> stencil_cases{{
{0, 1, 0x35353535u}, {0, 3, 0x57575757u},
{2, 1, 0x79797979u}, {0, 1, 0xa5a5a5a5u}}};
for (uint32_t pass = 0; pass < stencil_cases.size(); pass++) {
const auto [first, layers, stencil_word] = stencil_cases[pass];
auto partial_stencil_desc = layered_stencil_desc;
partial_stencil_desc.info.data.address += first * 4 * sizeof(float);
partial_stencil_desc.info.data.size = layers * 4 * sizeof(float);
partial_stencil_desc.info.stencil.address += first * stencil_slice_size;
partial_stencil_desc.info.stencil.size = layers * stencil_slice_size;
partial_stencil_desc.info.resources.layers = layers;
partial_stencil_desc.info.mip_layout[0].size =
partial_stencil_desc.info.data.size;
partial_stencil_desc.view_info.type = layers == 1
? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
partial_stencil_desc.view_info.layer_count = layers;
const auto stencil = partial_stencil_desc.info.stencil;
if (pass != 3) {
(void)resources.GetBufferCache().ObtainBuffer(stencil.address, stencil.size, true);
resources.GetBufferCache().FillBuffer(
stencil.address, stencil.size, stencil_word, false);
const auto partial_stencil_id = texture_cache.FindImage(partial_stencil_desc);
Require(name, "partial stencil retains depth owner",
partial_stencil_id == layered_stencil_id &&
texture_cache.GetImage(partial_stencil_id).info.resources.layers == 6 &&
partial_stencil_desc.view_info.base_layer == first &&
texture_cache.FindDepthTarget(partial_stencil_id,
partial_stencil_desc) != nullptr,
"a partial stencil view replaced its six-layer depth owner");
} else {
// The old prefix association must survive the owner's shifted binding.
Require(name, "partial stencil buffer clear",
texture_cache.ClearImageFromBuffer(scheduler.Current(),
stencil.address, stencil.size, stencil_word),
"the existing partial stencil association could not be cleared");
const auto rebound = texture_cache.FindImage(partial_stencil_desc);
Require(name, "rebind cleared stencil association",
rebound == layered_stencil_id &&
texture_cache.FindDepthTarget(rebound, partial_stencil_desc) != nullptr,
"the cleared stencil association lost its depth owner");
}
std::fill_n(stencil_expected.begin() + first, layers, stencil_word);
auto readback = CreateHostBuffer(name, 30 * sizeof(uint32_t),
vk::BufferUsageFlagBits::eTransferDst, {});
std::vector<vk::BufferImageCopy> copies(2);
copies[0].imageSubresource = {vk::ImageAspectFlagBits::eDepth, 0, 0, 6};
copies[1].bufferOffset = 24 * sizeof(float);
copies[1].imageSubresource = {vk::ImageAspectFlagBits::eStencil, 0, 0, 6};
for (auto &copy : copies) copy.imageExtent = {4, 1, 1};
texture_cache.GetImage(layered_stencil_id).Download(
copies, readback.buffer, 0, readback.size);
HostReadBarrier(readback.buffer, readback.size,
vk::PipelineStageFlagBits::eTransfer,
vk::AccessFlagBits::eTransferWrite);
scheduler.Finish();
const auto words = ReadBuffer(name, readback, 30);
for (uint32_t layer = 0; layer < 6; layer++) {
for (uint32_t pixel = 0; pixel < 4; pixel++) {
Require(name, "partial stencil preserves depth",
words[layer * 4 + pixel] == std::bit_cast<uint32_t>(0.75f),
"stencil upload changed the retained depth plane");
}
Require(name, "partial stencil preserves other layers",
words[24 + layer] == stencil_expected[layer],
"stencil upload used the retained depth allocation's layer count");
}
DestroyBuffer(&readback);
}
auto native_array_info = array_desc.info;
native_array_info.data = {};
auto native_volume_info = volume_desc.info;