graphics: support 3D color render targets

This commit is contained in:
nmzik
2026-08-02 09:38:57 +02:00
parent b877b4be9c
commit 59b8fad341
5 changed files with 245 additions and 50 deletions
+12 -6
View File
@@ -867,22 +867,28 @@ TextureCache::BuildColorTransfer(const Image& image, BindingType binding,
case BindingType::Texture: break;
case BindingType::Storage: owner = "StorageTextureCache"; break;
case BindingType::RenderTarget:
if (info.resources.layers == 0 || info.data.size % info.resources.layers != 0 ||
info.samples != 1 || image.backing.samples != 1) {
EXIT("TextureCache: invalid color-attachment upload\n");
}
format = ImageOps::RenderTargetTransferFormat(info.bytes_per_block);
allow_depth_tile = false;
plan.swap_bgra16 = info.bgra16;
owner = "RenderTarget";
break;
case BindingType::VideoOut:
if (info.resources.layers == 0 || info.data.size % info.resources.layers != 0 ||
info.samples != 1 || image.backing.samples != 1 ||
(binding == BindingType::VideoOut &&
info.metadata.compression != VideoOutCompression::Uncompressed)) {
info.metadata.compression != VideoOutCompression::Uncompressed) {
EXIT("TextureCache: invalid color-attachment upload\n");
}
format = binding == BindingType::RenderTarget
? ImageOps::RenderTargetTransferFormat(info.bytes_per_block)
: info.guest_format;
format = info.guest_format;
layers = info.resources.layers;
volume = false;
layered = layers > 1;
allow_depth_tile = false;
plan.swap_bgra16 = info.bgra16;
owner = binding == BindingType::RenderTarget ? "RenderTarget" : "VideoOut";
owner = "VideoOut";
break;
case BindingType::DepthTarget: return plan;
}
@@ -119,6 +119,17 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
uint32_t pitch = 0;
uint64_t size = 0;
bool tile = false;
const bool volume = rt.attrib3.dimension == 2;
if (rt.attrib3.dimension != 1 && !volume) {
EXIT("unsupported render-target dimension: %u\n", rt.attrib3.dimension);
}
if (!volume && rt.attrib3.depth != 0) {
EXIT("2D render target has nonzero depth: %u\n", rt.attrib3.depth);
}
if (volume && samples != 1) {
EXIT("multisampled 3D render targets are unsupported\n");
}
const uint32_t depth = volume ? rt.attrib3.depth + 1u : 1u;
const bool standard64 =
rt.attrib3.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB);
@@ -145,6 +156,7 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
if (bytes_per_element == 0) {
EXIT("render-target format has no valid element size\n");
}
const auto transfer_format = ImageOps::RenderTargetTransferFormat(bytes_per_element);
if (standard64 &&
(rt.attrib3.dimension != 1 || rt.attrib3.depth != 0 || levels != 1 ||
rt.view.current_mip_level != 0 || view.base_layer != 0 || view.image_layers != 1 ||
@@ -165,10 +177,14 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
if (rt.pitch.pitch_div8_minus1 != 0) {
pitch = (rt.pitch.pitch_div8_minus1 + 1u) << 3u;
} else if (tile) {
pitch = standard64
? TileGetTexturePitch(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float),
width, levels, rt.attrib3.tile_mode)
: TileGetRenderTargetPitch(width, bytes_per_element, rt.attrib.num_fragments);
if (volume) {
pitch = TileGetTexturePitch(transfer_format, width, levels, rt.attrib3.tile_mode);
} else if (standard64) {
pitch = TileGetTexturePitch(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float),
width, levels, rt.attrib3.tile_mode);
} else {
pitch = TileGetRenderTargetPitch(width, bytes_per_element, rt.attrib.num_fragments);
}
if (pitch == 0) {
EXIT("unsupported render-target pitch: width=%u bytes=%u\n", width, bytes_per_element);
}
@@ -178,7 +194,17 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
TileSizeOffset mip_sizes[16] {};
TilePaddedSize mip_padded[16] {};
if (tile) {
TileVolumeLayout volume_layout {};
uint64_t backing_size = 0;
if (volume) {
if (!tile || !TileGetTextureVolumeLayout(transfer_format, width, height, depth, levels,
rt.attrib3.tile_mode, volume_layout)) {
EXIT("unsupported 3D render-target layout: %ux%ux%u levels=%u tile=%u\n", width, height,
depth, levels, rt.attrib3.tile_mode);
}
size = volume_layout.block_slice_size;
backing_size = volume_layout.total_size;
} else if (tile) {
TileSizeAlign layout {};
bool valid_layout = false;
if (standard64) {
@@ -203,12 +229,6 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
mip_sizes[0] = {static_cast<uint32_t>(size), 0, 0, 0, 0, 0};
mip_padded[0] = {pitch, height};
}
if (rt.slice.slice_div64_minus1 != 0 &&
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u != size) {
EXIT("render-target slice span mismatch: encoded=0x%016" PRIx64 " derived=0x%016" PRIx64
"\n",
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u, size);
}
} else {
size = static_cast<uint64_t>(pitch) * height * bytes_per_element * samples;
if (size > UINT32_MAX) {
@@ -217,23 +237,40 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
mip_sizes[0] = {static_cast<uint32_t>(size), 0, 0, 0, 0, 0};
mip_padded[0] = {pitch, height};
}
if (size == 0 || size > UINT64_MAX / view.image_layers) {
if (rt.slice.slice_div64_minus1 != 0 &&
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u != size) {
EXIT("render-target slice span mismatch: encoded=0x%016" PRIx64 " derived=0x%016" PRIx64
"\n",
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u, size);
}
if (size == 0 || (!volume && size > UINT64_MAX / view.image_layers)) {
EXIT("render-target memory footprint is invalid\n");
}
const auto backing_size = size * view.image_layers;
if (!volume) {
backing_size = size * view.image_layers;
}
if (backing_size == 0) {
EXIT("render-target backing is empty\n");
}
if (backing_size > TRACKER_ADDRESS_SIZE - rt.base.addr) {
EXIT("render-target backing range is invalid\n");
}
const vk::Extent2D view_extent = {std::max(width >> rt.view.current_mip_level, 1u),
std::max(height >> rt.view.current_mip_level, 1u)};
const uint32_t view_depth = std::max(depth >> rt.view.current_mip_level, 1u);
if (volume &&
(view.base_layer >= view_depth || view.layer_count > view_depth - view.base_layer)) {
EXIT("3D render-target view exceeds mip depth: base=%u count=%u depth=%u mip=%u\n",
view.base_layer, view.layer_count, view_depth, rt.view.current_mip_level);
}
auto decision_log_id = g_render_color_log_count.fetch_add(1);
if (decision_log_id < 128) {
LOGF("RenderColorTarget: slot=%" PRIu32 " addr=0x%010" PRIx64 " size=0x%016" PRIx64
" extent=%ux%u view_mip=%u view_extent=%ux%u levels=%u pitch=%u"
" extent=%ux%ux%u view_mip=%u view_extent=%ux%u levels=%u pitch=%u"
" fmt=0x%08" PRIx32 " nfmt=0x%08" PRIx32 " order=0x%08" PRIx32 " samples=%u tile=%s\n",
rt_slot, rt.base.addr, backing_size, width, height, rt.view.current_mip_level,
rt_slot, rt.base.addr, backing_size, width, height, depth, rt.view.current_mip_level,
view_extent.width, view_extent.height, levels, pitch, rt.info.format,
rt.info.channel_type, rt.info.channel_order, samples, tile ? "tiled" : "linear");
}
@@ -242,15 +279,24 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
desc.type = TextureCache::BindingType::RenderTarget;
desc.info.data = {rt.base.addr, backing_size};
desc.info.pixel_format = target_format.format;
desc.info.guest_format = ImageOps::RenderTargetTransferFormat(bytes_per_element);
desc.info.type = Prospero::ImageType::kColor2D;
desc.info.extent = {width, height, 1};
desc.info.resources = {levels, view.image_layers};
desc.info.guest_format = transfer_format;
desc.info.type = volume ? Prospero::ImageType::kColor3D : Prospero::ImageType::kColor2D;
desc.info.extent = {width, height, depth};
desc.info.resources = {levels, volume ? 1u : view.image_layers};
desc.info.pitch = pitch;
desc.info.bytes_per_block = bytes_per_element;
desc.info.samples = samples;
desc.info.tile_mode = rt.attrib3.tile_mode;
for (uint32_t level = 0; level < levels; level++) {
if (volume) {
desc.info.mip_layout[level] = {
volume_layout.level_offsets[level],
volume_layout.level_sizes[level],
volume_layout.level_widths[level],
volume_layout.level_heights[level],
};
continue;
}
const auto level_offset =
mip_sizes[level].src_size != 0 ? mip_sizes[level].src_offset : mip_sizes[level].offset;
const auto level_size =
-18
View File
@@ -359,30 +359,12 @@ static void RtCheck(const HW::RenderTarget& rt) {
logged = true;
}
}
if (rt.attrib3.depth != 0x00000000) {
static bool logged = false;
if (!logged) {
LOGF("RenderTarget: temporary: ignoring PS5 color target depth_minus1=0x%08" PRIx32
"\n",
rt.attrib3.depth);
logged = true;
}
}
if (!RenderIsColorTileMode(rt.attrib3.tile_mode)) {
EXIT("unknown PS5 render-target tile mode: 0x%08" PRIx32 "\n", rt.attrib3.tile_mode);
}
if (!RenderIsColorDimension(rt.attrib3.dimension)) {
EXIT("unknown PS5 render-target dimension: 0x%08" PRIx32 "\n", rt.attrib3.dimension);
}
if (rt.attrib3.dimension != 0x00000001) {
static bool logged = false;
if (!logged) {
LOGF("RenderTarget: temporary: using 2D fallback for PS5 color "
"dimension=0x%08" PRIx32 "\n",
rt.attrib3.dimension);
logged = true;
}
}
if (!rt.attrib3.cmask_pipe_aligned) {
static bool logged = false;
if (!logged) {
@@ -105,6 +105,10 @@ Image::Barriers Image::GetBarriers(vk::ImageLayout destinat
std::optional<ImageSubresourceRange> range) {
auto& state = backing.state;
auto& subresource_states = backing.subresource_states;
if (range && info.IsVolume()) {
range->base_layer = 0;
range->layer_count = 1;
}
const bool partial =
range && (range->base_level != 0 || range->level_count != info.resources.levels ||
+157
View File
@@ -248,6 +248,12 @@ struct RenderExecutorTestAccess {
executor.ResolveRenderDepthTarget(submit_id, buffer, depth);
}
static void ResolveRenderColorTarget(RenderExecutor& executor, uint64_t submit_id,
RenderCommandBuffer& buffer, RenderColorInfo& color,
uint32_t slot) {
executor.ResolveRenderColorTarget(submit_id, buffer, color, 0, slot);
}
static void BindRenderTarget(RenderExecutor& executor, ImageId id) {
executor.BindRenderTarget(id);
}
@@ -4751,6 +4757,155 @@ public:
std::printf("[gpu] %-32s ok\n", name);
}
void CheckRenderExecutorColorVolumeDiscovery() {
constexpr const char* name = "RenderExecutorColorVolumeDiscovery";
constexpr uintptr_t base = 0x0000000203e00000ull;
constexpr uint64_t allocation_size = 0x200000;
constexpr uint64_t allocation_alignment = 0x10000;
EnsureRuntimeContext();
int64_t direct_offset = -1;
Require(name, "direct allocation",
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), allocation_size,
allocation_alignment, 0, &direct_offset) == 0,
"color-volume direct-memory allocation failed");
void* mapped = reinterpret_cast<void*>(base);
Require(name, "direct mapping",
Libs::LibKernel::Memory::KernelMapDirectMemory(&mapped, allocation_size, 0x3, 0x10,
direct_offset,
allocation_alignment) == 0 &&
mapped == reinterpret_cast<void*>(base),
"color-volume fixed mapping failed");
std::memset(mapped, 0, allocation_size);
constexpr uint64_t slice_size = 0x10000;
std::memset(static_cast<uint8_t*>(mapped) + 31 * slice_size, 0x5a, slice_size);
{
RenderContext context(m_runtime_context);
auto& scheduler = context.GetCommandScheduler();
HW::Context registers {};
HW::UserConfig user_config {};
HW::Shader shaders {};
registers.SetColorBase(0, {.addr = base});
registers.SetColorInfo(
0, {.format = Prospero::GpuEnumValue(Prospero::ChannelLayout::k10_10_10_2),
.channel_type = Prospero::GpuEnumValue(Prospero::ChannelType::kUNorm),
.channel_order = Prospero::GpuEnumValue(Prospero::ChannelOrder::kStandard)});
registers.SetColorAttrib2(0, {.height = 31, .width = 31});
registers.SetColorAttrib3(
0, {.depth = 31,
.tile_mode = Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget),
.dimension = 2,
.cmask_pipe_aligned = true,
.dcc_pipe_aligned = true});
registers.SetRenderTargetMask(0x0f);
scheduler.Begin(registers, user_config, shaders);
auto& resources = context.GetGpuResources();
auto& texture_cache = resources.GetTextureCache();
auto& executor = context.GetRenderExecutor();
resources.MapMemory(base, allocation_size);
RenderColorInfo color {};
RenderExecutorTestAccess::ResolveRenderColorTarget(executor, 1, scheduler.Current(),
color, 0);
const auto attachment = texture_cache.FindRenderTarget(color.image_id, color.desc);
const auto& image = texture_cache.GetImage(color.image_id);
Require(name, "captured 3D target",
color.image_id && attachment != nullptr &&
color.desc.info.type == Prospero::ImageType::kColor3D &&
color.desc.info.extent == vk::Extent3D {32, 32, 32} &&
color.desc.info.resources == ImageSubresources {1, 1} &&
color.desc.info.pitch == 128 &&
color.desc.info.data.size == allocation_size &&
color.desc.info.mip_layout[0].size == 0x10000 &&
color.desc.view_info.type == vk::ImageViewType::e2D &&
color.desc.view_info.layer_count == 1 &&
image.backing.image_type == vk::ImageType::e3D &&
static_cast<bool>(image.backing.flags &
vk::ImageCreateFlagBits::e2DArrayCompatible) &&
image.usage.render_target && image.IsGpuModified(),
"dimension=2/depth=31 did not create the SDK-defined 32x32x32 "
"backing and 2D "
"attachment slice");
RenderExecutorTestAccess::ResetBindings(executor);
registers.SetColorView(
0, {.base_array_slice_index = 7, .last_array_slice_index = 7});
RenderColorInfo sliced_color {};
RenderExecutorTestAccess::ResolveRenderColorTarget(
executor, 2, scheduler.Current(), sliced_color, 0);
RenderDepthInfo no_depth {};
const auto sliced_rendering = RenderExecutorTestAccess::AcquireRenderTargets(
executor, scheduler.Current(), &sliced_color, 1, no_depth);
Require(name, "3D slice transition",
sliced_color.image_id == color.image_id && sliced_color.image_view != nullptr &&
sliced_color.desc.view_info.base_layer == 7 &&
sliced_rendering.num_color_attachments == 1 && sliced_rendering.num_layers == 1,
"a nonzero 3D attachment slice was treated as a Vulkan array layer");
auto storage_desc = color.desc;
storage_desc.type = BindingType::Storage;
storage_desc.info.guest_format =
Prospero::GpuEnumValue(Prospero::BufferFormat::k10_10_10_2UNorm);
storage_desc.view_info.type = vk::ImageViewType::e3D;
storage_desc.view_info.usage = vk::ImageUsageFlagBits::eStorage;
const auto storage_id = texture_cache.FindImage(storage_desc);
const auto storage_view = texture_cache.FindTexture(storage_id, storage_desc);
const auto& shared_image = texture_cache.GetImage(storage_id);
Require(name, "storage alias reuse",
storage_id == color.image_id && storage_view != nullptr &&
shared_image.IsGpuModified() && shared_image.usage.render_target,
"the matching 3D storage binding did not reuse the live "
"render-target image");
Require(name, "volume readback queue",
TextureCacheTestAccess::TryDownload(texture_cache, storage_id),
"the 3D render target could not be queued for guest-layout readback");
auto mirror = resources.GetBufferCache().ObtainBuffer(
scheduler.Current(), base, allocation_size, false, true, true);
Require(name, "volume mirror",
mirror.buffer != nullptr && mirror.owner != nullptr,
"the 3D render-target readback has no BufferCache owner");
scheduler.Current().RetainResourceUntilFence(mirror.owner);
auto slice_probe =
CreateHostBuffer(name, 4, vk::BufferUsageFlagBits::eTransferDst, {0});
const vk::BufferCopy copy {mirror.offset + 31 * slice_size, 0, 4};
scheduler.Current().Handle().copyBuffer(mirror.buffer, slice_probe.buffer, 1, &copy);
vk::BufferMemoryBarrier barrier {};
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
barrier.dstAccessMask = vk::AccessFlagBits::eHostRead;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.buffer = slice_probe.buffer;
barrier.size = slice_probe.size;
scheduler.Current().Handle().pipelineBarrier(
vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eHost, {}, 0, nullptr,
1, &barrier, 0, nullptr);
scheduler.Finish();
scheduler.DrainPriorityOperations();
Require(name, "volume Z transfer",
ReadBuffer(name, slice_probe, 1) == std::vector<u32> {0x5a5a5a5a},
"render-target upload/readback lost the final Z slice");
DestroyBuffer(&slice_probe);
RenderExecutorTestAccess::ResetBindings(executor);
resources.UnmapMemory(base, allocation_size);
scheduler.Finish();
}
Require(name, "unmap direct backing",
Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0,
"color-volume direct mapping release failed");
Require(
name, "release direct backing",
Libs::LibKernel::Memory::KernelReleaseDirectMemory(direct_offset, allocation_size) == 0,
"color-volume direct-memory allocation release failed");
std::printf("[gpu] %-32s ok\n", name);
}
void CheckRenderExecutorStencilBindingDiscovery() {
constexpr const char* name = "RenderExecutorStencilBindingDiscovery";
constexpr uintptr_t base = 0x0000000203600000ull;
@@ -17075,6 +17230,7 @@ int main(int argc, char** argv) {
CheckDepthAttachmentWrites();
CheckDynamicRenderingState();
VulkanHarness vulkan;
vulkan.CheckRenderExecutorColorVolumeDiscovery();
vulkan.CheckRenderExecutorStencilBindingDiscovery();
vulkan.CheckUnifiedTextureCacheFlow();
vulkan.CheckBgra16Readback();
@@ -17215,6 +17371,7 @@ int main(int argc, char** argv) {
vulkan.CheckCommandPoolGrowth();
vulkan.CheckGpuTilerCpuParity();
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
vulkan.CheckRenderExecutorColorVolumeDiscovery();
vulkan.CheckRenderExecutorStencilBindingDiscovery();
vulkan.CheckUnifiedTextureCacheFlow();
vulkan.CheckBgra16Readback();