mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 02:47:02 +02:00
graphics: share packed texture and render-target channel layouts
Resolve PS5 5551 textures with alpha in bit 15 and compose host channel order with guest selectors. Share the existing render-target mapping with sampled textures so packed layouts cannot diverge. Add native GPU readback coverage for 5551 and 4444, arbitrary selectors, constants, and existing 1555 render-target mapping. Verified PPSA24515 through launch.json with Vulkan validation, advancing scenes and over 12,700 guest flips. Independent correctness and cleanliness audits passed.
This commit is contained in:
@@ -28,13 +28,13 @@ constexpr Prospero::ColorComponentMapping kRenderTargetColorMappings[4][4] = {
|
||||
Prospero::ColorMappingArgb},
|
||||
};
|
||||
|
||||
struct RenderTargetHostFormatInfo {
|
||||
struct HostFormatInfo {
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
Prospero::ColorComponentMapping host_to_storage;
|
||||
};
|
||||
|
||||
RenderTargetHostFormatInfo ResolveRenderTargetHostFormat(Prospero::BufferFormat guest_format,
|
||||
Prospero::ChannelOrder order) {
|
||||
HostFormatInfo ResolveHostFormat(Prospero::BufferFormat guest_format,
|
||||
Prospero::ChannelOrder order) {
|
||||
if (order == Prospero::ChannelOrder::kAlt) {
|
||||
switch (guest_format) {
|
||||
case Prospero::BufferFormat::k8_8_8_8UNorm:
|
||||
@@ -48,14 +48,12 @@ RenderTargetHostFormatInfo ResolveRenderTargetHostFormat(Prospero::BufferFormat
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
const auto format = VulkanFormat(guest_format);
|
||||
switch (guest_format) {
|
||||
case Prospero::BufferFormat::k5_5_5_1UNorm:
|
||||
return {vk::Format::eA1R5G5B5UnormPack16, Prospero::ColorMappingBgra};
|
||||
case Prospero::BufferFormat::k5_5_5_1UNorm: return {format, Prospero::ColorMappingBgra};
|
||||
case Prospero::BufferFormat::k1_5_5_5UNorm:
|
||||
return {vk::Format::eR5G5B5A1UnormPack16, Prospero::ColorMappingAbgr};
|
||||
case Prospero::BufferFormat::k4_4_4_4UNorm:
|
||||
return {vk::Format::eR4G4B4A4UnormPack16, Prospero::ColorMappingAbgr};
|
||||
default: return {VulkanFormat(guest_format), {}};
|
||||
case Prospero::BufferFormat::k4_4_4_4UNorm: return {format, Prospero::ColorMappingAbgr};
|
||||
default: return {format, {}};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +64,7 @@ RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layo
|
||||
Prospero::ChannelOrder order) {
|
||||
const auto encoding = Prospero::ResolveRenderTargetFormat(layout, type);
|
||||
if (encoding.IsValid() && encoding.SupportsOrder(order)) {
|
||||
const auto host = ResolveRenderTargetHostFormat(encoding.buffer_format, order);
|
||||
const auto host = ResolveHostFormat(encoding.buffer_format, order);
|
||||
const auto bytes = Prospero::RenderTargetBytesPerElement(encoding.buffer_format);
|
||||
if (host.format != vk::Format::eUndefined && bytes != 0) {
|
||||
const auto order_mapping =
|
||||
@@ -92,7 +90,11 @@ static size_t GetTextureRegionCount(uint32_t depth, uint64_t levels, bool volume
|
||||
return count;
|
||||
}
|
||||
|
||||
vk::ComponentSwizzle TextureGetComponentSwizzle(uint8_t s) {
|
||||
vk::ComponentSwizzle TextureGetComponentSwizzle(uint8_t s,
|
||||
Prospero::ColorComponentMapping storage_to_host) {
|
||||
if (s >= 4u && s <= 7u) {
|
||||
s = static_cast<uint8_t>(4u + storage_to_host.Map(s - 4u));
|
||||
}
|
||||
switch (static_cast<Prospero::CompSwizzle>(s)) {
|
||||
case Prospero::CompSwizzle::kZero: return vk::ComponentSwizzle::eZero;
|
||||
case Prospero::CompSwizzle::kOne: return vk::ComponentSwizzle::eOne;
|
||||
@@ -102,30 +104,33 @@ vk::ComponentSwizzle TextureGetComponentSwizzle(uint8_t s) {
|
||||
case Prospero::CompSwizzle::kAlpha: return vk::ComponentSwizzle::eA;
|
||||
default: EXIT("unknown swizzle: %d\n", static_cast<int>(s));
|
||||
}
|
||||
return vk::ComponentSwizzle::eIdentity;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle) {
|
||||
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle,
|
||||
Prospero::ColorComponentMapping host_to_storage) {
|
||||
Prospero::ColorComponentMapping storage_to_host {0u};
|
||||
for (uint32_t host = 0; host < 4u; ++host) {
|
||||
storage_to_host.packed |= static_cast<uint8_t>(host << (host_to_storage.Map(host) * 2u));
|
||||
}
|
||||
vk::ComponentMapping components {};
|
||||
components.r = TextureGetComponentSwizzle(GetDstSel(swizzle, 0));
|
||||
components.g = TextureGetComponentSwizzle(GetDstSel(swizzle, 1));
|
||||
components.b = TextureGetComponentSwizzle(GetDstSel(swizzle, 2));
|
||||
components.a = TextureGetComponentSwizzle(GetDstSel(swizzle, 3));
|
||||
components.r = TextureGetComponentSwizzle(GetDstSel(swizzle, 0), storage_to_host);
|
||||
components.g = TextureGetComponentSwizzle(GetDstSel(swizzle, 1), storage_to_host);
|
||||
components.b = TextureGetComponentSwizzle(GetDstSel(swizzle, 2), storage_to_host);
|
||||
components.a = TextureGetComponentSwizzle(GetDstSel(swizzle, 3), storage_to_host);
|
||||
return components;
|
||||
}
|
||||
|
||||
SurfaceFormatInfo TextureGetSurfaceFormatInfo(Prospero::BufferFormat format) {
|
||||
const auto backing_format = Prospero::RemapTextureFormat(format);
|
||||
const auto vk_format = VulkanFormat(backing_format);
|
||||
const auto backing_format = Prospero::RemapTextureFormat(format);
|
||||
const auto host = ResolveHostFormat(backing_format, Prospero::ChannelOrder::kStandard);
|
||||
const auto conversion_format =
|
||||
backing_format != format ? format : Prospero::BufferFormat::kInvalid;
|
||||
if (vk_format != vk::Format::eUndefined) {
|
||||
return SurfaceFormatInfo(vk_format, conversion_format);
|
||||
if (host.format != vk::Format::eUndefined) {
|
||||
return {host.format, conversion_format, host.host_to_storage};
|
||||
}
|
||||
EXIT("unknown format: fmt = %u\n", static_cast<uint32_t>(format));
|
||||
return SurfaceFormatInfo(vk::Format::eUndefined, Prospero::BufferFormat::kInvalid);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -20,12 +20,9 @@ struct RenderTargetFormatInfo {
|
||||
};
|
||||
|
||||
struct SurfaceFormatInfo {
|
||||
explicit constexpr SurfaceFormatInfo(vk::Format format,
|
||||
Prospero::BufferFormat conversion_format)
|
||||
: vk_format(format), conversion_format(conversion_format) {}
|
||||
|
||||
vk::Format vk_format;
|
||||
Prospero::BufferFormat conversion_format;
|
||||
vk::Format vk_format;
|
||||
Prospero::BufferFormat conversion_format;
|
||||
Prospero::ColorComponentMapping host_to_storage;
|
||||
};
|
||||
|
||||
struct TextureUploadMipLayout {
|
||||
@@ -43,7 +40,8 @@ struct TextureUploadLayout {
|
||||
TextureUploadMipLayout mips[16] = {};
|
||||
};
|
||||
|
||||
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle);
|
||||
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle,
|
||||
Prospero::ColorComponentMapping host_to_storage);
|
||||
SurfaceFormatInfo TextureGetSurfaceFormatInfo(Prospero::BufferFormat format);
|
||||
RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layout,
|
||||
Prospero::ChannelType type,
|
||||
|
||||
@@ -483,17 +483,18 @@ static void PopulateTextureMipLayout(ImageInfo& info) {
|
||||
|
||||
static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderTextureResource& descriptor, vk::Format format,
|
||||
bool shader_conversion, bool storage, uint32_t view_levels,
|
||||
uint32_t image_layers) {
|
||||
const SurfaceFormatInfo& surface_format, bool storage,
|
||||
uint32_t view_levels, uint32_t image_layers) {
|
||||
ImageViewInfo view {};
|
||||
view.format = format;
|
||||
view.aspect = vk::ImageAspectFlagBits::eColor;
|
||||
view.base_level = descriptor.BaseLevel();
|
||||
view.level_count = view_levels;
|
||||
view.usage = storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlagBits::eSampled;
|
||||
view.mapping = storage || shader_conversion
|
||||
? vk::ComponentMapping {}
|
||||
: TextureGetComponentMapping(descriptor.DstSelXYZW());
|
||||
view.usage = storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlagBits::eSampled;
|
||||
view.mapping =
|
||||
storage || surface_format.conversion_format != Prospero::BufferFormat::kInvalid
|
||||
? vk::ComponentMapping {}
|
||||
: TextureGetComponentMapping(descriptor.DstSelXYZW(), surface_format.host_to_storage);
|
||||
switch (resource.dimension) {
|
||||
case ShaderRecompiler::Decoder::ImageDimension::Dim1D:
|
||||
view.type = vk::ImageViewType::e1D;
|
||||
@@ -672,7 +673,7 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
} else {
|
||||
PopulateTextureMipLayout(desc.info);
|
||||
}
|
||||
desc.view_info = TextureViewInfo(resource, descriptor, view_format, shader_conversion, storage,
|
||||
desc.view_info = TextureViewInfo(resource, descriptor, view_format, surface_format, storage,
|
||||
view_levels, desc.info.resources.layers);
|
||||
desc.type = storage ? TextureCache::BindingType::Storage : TextureCache::BindingType::Texture;
|
||||
|
||||
|
||||
@@ -78,7 +78,8 @@ constexpr FormatMapping kFormatMappings[] = {
|
||||
{Prospero::BufferFormat::k8_8_8_8Srgb, vk::Format::eR8G8B8A8Srgb},
|
||||
{Prospero::BufferFormat::k9_9_9_5Float, vk::Format::eE5B9G9R9UfloatPack32},
|
||||
{Prospero::BufferFormat::k5_6_5UNorm, vk::Format::eB5G6R5UnormPack16},
|
||||
{Prospero::BufferFormat::k5_5_5_1UNorm, vk::Format::eR5G5B5A1UnormPack16},
|
||||
{Prospero::BufferFormat::k5_5_5_1UNorm, vk::Format::eA1R5G5B5UnormPack16},
|
||||
{Prospero::BufferFormat::k1_5_5_5UNorm, vk::Format::eR5G5B5A1UnormPack16},
|
||||
{Prospero::BufferFormat::k4_4_4_4UNorm, vk::Format::eR4G4B4A4UnormPack16},
|
||||
{Prospero::BufferFormat::kBc1UNorm, vk::Format::eBc1RgbaUnormBlock},
|
||||
{Prospero::BufferFormat::kBc1Srgb, vk::Format::eBc1RgbaSrgbBlock},
|
||||
|
||||
@@ -8506,6 +8506,138 @@ public:
|
||||
std::printf("[gpu] %-32s ok\n", name);
|
||||
}
|
||||
|
||||
void CheckPackedTextureComponents() {
|
||||
constexpr const char *name = "PackedTextureComponents";
|
||||
constexpr uintptr_t base = 0x0000000204200000ull;
|
||||
constexpr uint64_t allocation_size = 0x10000;
|
||||
struct FormatCase {
|
||||
Prospero::BufferFormat format;
|
||||
std::array<uint16_t, 4> words;
|
||||
std::array<std::array<uint32_t, 4>, 4> channels;
|
||||
std::array<uint32_t, 4> maxima;
|
||||
};
|
||||
// Packed format channels start at the least significant bit.
|
||||
// PPSA24515's blue, white, and masked black exposed the 5551 reversal.
|
||||
constexpr std::array cases{
|
||||
FormatCase{Prospero::BufferFormat::k5_5_5_1UNorm,
|
||||
{0x7c00, 0x7fff, 0x8000, 0x8c41},
|
||||
{{{0, 0, 31, 0}, {31, 31, 31, 0}, {0, 0, 0, 1}, {1, 2, 3, 1}}},
|
||||
{31, 31, 31, 1}},
|
||||
FormatCase{Prospero::BufferFormat::k4_4_4_4UNorm,
|
||||
{0x000f, 0x00f0, 0x0f00, 0x4321},
|
||||
{{{15, 0, 0, 0}, {0, 15, 0, 0}, {0, 0, 15, 0}, {1, 2, 3, 4}}},
|
||||
{15, 15, 15, 15}},
|
||||
};
|
||||
const auto argb = TextureGetComponentMapping(DstSel(5, 1, 7, 0),
|
||||
Prospero::ColorMappingArgb);
|
||||
Require(name, "inverse mapping",
|
||||
argb == vk::ComponentMapping{vk::ComponentSwizzle::eB,
|
||||
vk::ComponentSwizzle::eOne, vk::ComponentSwizzle::eR,
|
||||
vk::ComponentSwizzle::eZero},
|
||||
"cyclic host mapping reordered output slots or descriptor constants");
|
||||
const auto target = TextureGetRenderTargetFormat(
|
||||
Prospero::ChannelLayout::k1_5_5_5, Prospero::ChannelType::kUNorm,
|
||||
Prospero::ChannelOrder::kStandard);
|
||||
Require(name, "1555 render target",
|
||||
target.format == vk::Format::eR5G5B5A1UnormPack16 &&
|
||||
target.export_mapping == Prospero::ColorMappingAbgr,
|
||||
"shared format resolution changed the existing 1555 render target");
|
||||
EnsureRuntimeContext();
|
||||
int64_t direct_offset = -1;
|
||||
Require(name, "allocation",
|
||||
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
||||
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
|
||||
allocation_size, allocation_size, 0, &direct_offset) == 0,
|
||||
"packed texture allocation failed");
|
||||
void *mapped = reinterpret_cast<void *>(base);
|
||||
Require(name, "mapping",
|
||||
Libs::LibKernel::Memory::KernelMapDirectMemory(
|
||||
&mapped, allocation_size, 0x3, 0x10, direct_offset,
|
||||
allocation_size) == 0 && mapped == reinterpret_cast<void *>(base),
|
||||
"packed texture mapping failed");
|
||||
for (const auto &format : cases) {
|
||||
std::memset(mapped, 0, allocation_size);
|
||||
std::memcpy(mapped, format.words.data(), sizeof(format.words));
|
||||
RenderContext context(m_runtime_context);
|
||||
auto &scheduler = context.GetCommandScheduler();
|
||||
HW::Context registers{};
|
||||
HW::UserConfig user_config{};
|
||||
HW::Shader shaders{};
|
||||
scheduler.Begin(registers, user_config, shaders);
|
||||
auto &resources = context.GetGpuResources();
|
||||
auto &cache = resources.GetTextureCache();
|
||||
auto &executor = context.GetRenderExecutor();
|
||||
resources.MapMemory(base, allocation_size);
|
||||
for (const auto swizzle : {DstSel(4, 5, 6, 7), DstSel(7, 1, 4, 0)}) {
|
||||
ShaderTextureResource descriptor{{static_cast<uint32_t>(base >> 8u),
|
||||
(static_cast<uint32_t>(format.format) << 20u) | (3u << 30u), 0,
|
||||
swizzle | (static_cast<uint32_t>(Prospero::ImageType::kColor2D) << 28u),
|
||||
0, 0x00700000u, 0, 0}};
|
||||
TestCase test;
|
||||
test.name = name;
|
||||
test.has_user_data = true;
|
||||
test.image_descriptor_swizzle = swizzle;
|
||||
std::copy_n(descriptor.fields, 8, test.user_data.begin());
|
||||
test.user_data[50] = 16 * sizeof(uint32_t);
|
||||
test.opcodes = {ShaderOpcode::V_MOV_B32, ShaderOpcode::IMAGE_LOAD,
|
||||
ShaderOpcode::BUFFER_STORE_DWORD, ShaderOpcode::S_ENDPGM};
|
||||
for (uint32_t pixel = 0; pixel < format.words.size(); ++pixel) {
|
||||
AppendVMovU32(&test.code, 20, pixel);
|
||||
AppendVMovU32(&test.code, 21, 0);
|
||||
test.code.push_back(EncodeMimg0(0x00, 0xf));
|
||||
test.code.push_back(EncodeMimg1(0, 20));
|
||||
for (uint32_t channel = 0; channel < 4; ++channel) {
|
||||
AppendStoreVgpr(&test.code, channel, pixel * 4 + channel);
|
||||
}
|
||||
}
|
||||
AppendEnd(&test.code);
|
||||
const auto compiled = CompileCase(test, SubgroupSize());
|
||||
ShaderRecompiler::IR::DescriptorValue value{};
|
||||
value.dword_count = 8;
|
||||
std::copy_n(descriptor.fields, 8, value.dwords.begin());
|
||||
const auto binding = RenderExecutorTestAccess::ResolveTexture(
|
||||
executor, compiled.program.info.images.at(0), value);
|
||||
Image sampled;
|
||||
sampled.view = cache.FindTexture(binding.image_id, binding.desc);
|
||||
sampled.layout = cache.GetImage(binding.image_id).backing.state.layout;
|
||||
scheduler.Finish();
|
||||
auto output = CreateStorageBuffer(name, {}, 16);
|
||||
Dispatch(test, compiled, output, nullptr, &sampled);
|
||||
const auto actual = ReadBuffer(name, output, 16);
|
||||
for (uint32_t pixel = 0; pixel < format.words.size(); ++pixel) {
|
||||
for (uint32_t channel = 0; channel < 4; ++channel) {
|
||||
const auto selector = GetDstSel(swizzle, channel);
|
||||
const auto maximum = selector < 4 ? 1u : format.maxima[selector - 4];
|
||||
const auto expected = selector < 4 ? selector : format.channels[pixel][selector - 4];
|
||||
const float observed = std::bit_cast<float>(actual[pixel * 4 + channel]);
|
||||
// Recover the source bits without testing the host's UNORM rounding precision.
|
||||
const bool matches = maximum == 1 ? observed == expected
|
||||
: observed >= 0.0f && observed <= 1.0f &&
|
||||
std::round(observed * maximum) == expected;
|
||||
if (!matches) {
|
||||
std::ostringstream message;
|
||||
message << "format=" << static_cast<uint32_t>(format.format)
|
||||
<< " swizzle=" << swizzle << " word=" << format.words[pixel]
|
||||
<< " channel=" << channel << " expected=" << expected
|
||||
<< " actual=" << observed;
|
||||
Fail(name, "GPU components", message.str());
|
||||
}
|
||||
}
|
||||
}
|
||||
DestroyBuffer(&output);
|
||||
}
|
||||
RenderExecutorTestAccess::ResetBindings(executor);
|
||||
resources.UnmapMemory(base, allocation_size);
|
||||
scheduler.Finish();
|
||||
}
|
||||
Require(name, "release",
|
||||
Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0 &&
|
||||
Libs::LibKernel::Memory::KernelReleaseDirectMemory(
|
||||
direct_offset, allocation_size) == 0,
|
||||
"packed texture backing release failed");
|
||||
std::printf("[gpu] %-32s ok\n", name);
|
||||
}
|
||||
|
||||
void CheckComparisonDepthTexture() {
|
||||
constexpr const char *name = "ComparisonDepthTexture";
|
||||
constexpr uintptr_t base = 0x0000000204200000ull;
|
||||
@@ -25465,7 +25597,7 @@ void CheckSampledColorViews() {
|
||||
"valid PS5 sampled mappings were rejected or reserved selectors were "
|
||||
"admitted");
|
||||
const auto arbitrary = DstSel(5, 1, 7, 0);
|
||||
const auto components = TextureGetComponentMapping(arbitrary);
|
||||
const auto components = TextureGetComponentMapping(arbitrary, {});
|
||||
Require("SampledColorViews", "generic Vulkan component mapping",
|
||||
SelectSampledColorView(vk::Format::eR8G8B8A8Unorm,
|
||||
vk::Format::eR8G8B8A8Unorm,
|
||||
@@ -29073,6 +29205,11 @@ int main(int argc, char **argv) {
|
||||
std::setvbuf(stdout, nullptr, _IONBF, 0);
|
||||
EnsureConfigInitialized();
|
||||
CheckLeastRecentlyUsedCacheOrdering();
|
||||
if (argc == 2 && std::strcmp(argv[1], "--packed-texture-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
vulkan.CheckPackedTextureComponents();
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--error-dialog-only") == 0) {
|
||||
CheckErrorDialogLifecycle();
|
||||
return 0;
|
||||
@@ -29494,6 +29631,7 @@ int main(int argc, char **argv) {
|
||||
vulkan.CheckBufferCacheDirtyGarbageCollection();
|
||||
#endif
|
||||
vulkan.CheckUnifiedImageViewCache();
|
||||
vulkan.CheckPackedTextureComponents();
|
||||
const auto tests = MakeCases();
|
||||
const auto graphics_tests = MakeGraphicsCases();
|
||||
CheckOpcodeCoverage(tests, graphics_tests);
|
||||
|
||||
Reference in New Issue
Block a user