perf(agc): keep the 10-bit scanout resident and present it with the guest path's pixels
PPSA21564's final composite renders into 4K 10-bit scanout buffers (COLOR_2_10_10_10 with the alternate swap, A2R10G10B10). No guest texture format matched that color format, so the target stayed non-resident: every frame's composite draw ran synchronously (about 11 ms, 143 per 10 s at the title) and the flip read the frame back through guest memory. The blue-low 10-bit color target now keeps its bits in the storage image of its surface's red-low 2-10-10-10 format, as B8G8R8A8 targets map to R8G8B8A8. Presenting it: DisplayTexelFormat names a display buffer's texel order as the guest-memory path reads it (R8G8B8A8 base: red low, else blue low; the 10-bit variant: A2B10G10R10 or A2R10G10B10). ResidentPresentPath blits a resident image only when it holds the display's 8-bit texel format (an exact copy). A 10-bit image (whose blit would round where the guest path drops the low two bits), the other 8-bit order or an sRGB type is converted instead: GpuColorTransfer::DetileImage copies the image's texels raw and runs the guest path's own conversion shader on them, so both paths present the same pixels. The image's write-back is unchanged: its results stay pending like any resident target's and are stored when a reader of the memory flushes them. Tests: agc_driver_recorder_tests checks the format decision for both 8-bit and both 10-bit orders, and converts an image in each order, comparing it with the CPU decode and the GPU detile of the same words in guest memory.
This commit is contained in:
parent
48752c1132
commit
6142e82aa0
|
|
@ -49,6 +49,7 @@ add_custom_command(
|
|||
set(agcDriverSources
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Unimplemented.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/DisplayBuffer.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/DisplayFormat.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/Driver/DeviceAccess.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/Driver/Diagnostics.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/Driver/Dispatch/DataHitVerification.cpp
|
||||
|
|
@ -266,6 +267,7 @@ add_custom_command(
|
|||
|
||||
add_test_executable(agc_driver_recorder_tests EXCLUDE_FROM_ALL
|
||||
tests/Recorder.cpp
|
||||
tests/ResidentPresent.cpp
|
||||
${agcSampleLodShaderHeader}
|
||||
)
|
||||
|
||||
|
|
@ -453,6 +455,7 @@ if(AGC_BUILD_VISUAL_TEST)
|
|||
add_test_executable(agc_driver_visual_test EXCLUDE_FROM_ALL
|
||||
tests/Visual.cpp
|
||||
Execution/src/VulkanDevice.cpp
|
||||
Execution/src/DisplayFormat.cpp
|
||||
Execution/src/AspectFit.cpp
|
||||
Execution/src/PresentationScaler.cpp
|
||||
Execution/src/GuestMemory.cpp
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_DISPLAYFORMAT_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_DISPLAYFORMAT_HPP
|
||||
|
||||
#define VK_NO_PROTOTYPES
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <cstdint>
|
||||
|
||||
namespace AgcDriver {
|
||||
|
||||
VkFormat DisplayTexelFormat(std::uint64_t pixelFormat);
|
||||
bool DisplayTenBit(std::uint64_t pixelFormat);
|
||||
bool DisplayRedLow(std::uint64_t pixelFormat);
|
||||
|
||||
enum class ResidentPresent : std::uint8_t { None, Blit, Convert };
|
||||
ResidentPresent ResidentPresentPath(VkFormat storage, std::uint64_t pixelFormat, bool blitSource);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -304,7 +304,7 @@ private:
|
|||
// The tail of a dispatch's device call from the open batch's command buffer to the completion
|
||||
// registration: keeps, the template data refresh, barriers, bind, push, dispatch, marks.
|
||||
void recordDispatch(RecordedDispatch& record);
|
||||
bool present(std::uint32_t width, std::uint32_t height, bool opaque, std::span<const std::byte> pixels, const DisplayBuffer* display = nullptr, const std::shared_ptr<Graphics::StorageTexture>& resident = nullptr, VkFilter residentFilter = VK_FILTER_LINEAR, bool dumpFrame = false);
|
||||
bool present(std::uint32_t width, std::uint32_t height, bool opaque, std::span<const std::byte> pixels, const DisplayBuffer* display = nullptr, const std::shared_ptr<Graphics::StorageTexture>& resident = nullptr, VkFilter residentFilter = VK_FILTER_LINEAR, bool dumpFrame = false, bool residentConvert = false);
|
||||
struct State;
|
||||
std::unique_ptr<State> state;
|
||||
std::uint64_t serial;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/DisplayFormat.hpp"
|
||||
|
||||
namespace AgcDriver {
|
||||
namespace {
|
||||
|
||||
constexpr std::uint64_t PixelFormatUnormBit = 0x0100000000000000ull;
|
||||
constexpr std::uint64_t PixelFormatR8G8B8A8 = 0x8000000022000000ull;
|
||||
|
||||
}
|
||||
|
||||
bool DisplayTenBit(std::uint64_t pixelFormat) {
|
||||
return (pixelFormat & PixelFormatUnormBit) != 0;
|
||||
}
|
||||
|
||||
bool DisplayRedLow(std::uint64_t pixelFormat) {
|
||||
return (pixelFormat & ~PixelFormatUnormBit) == PixelFormatR8G8B8A8;
|
||||
}
|
||||
|
||||
VkFormat DisplayTexelFormat(std::uint64_t pixelFormat) {
|
||||
const bool redLow = DisplayRedLow(pixelFormat);
|
||||
if (DisplayTenBit(pixelFormat)) return redLow ? VK_FORMAT_A2B10G10R10_UNORM_PACK32 : VK_FORMAT_A2R10G10B10_UNORM_PACK32;
|
||||
return redLow ? VK_FORMAT_R8G8B8A8_UNORM : VK_FORMAT_B8G8R8A8_UNORM;
|
||||
}
|
||||
|
||||
ResidentPresent ResidentPresentPath(VkFormat storage, std::uint64_t pixelFormat, bool blitSource) {
|
||||
const bool tenBit = DisplayTenBit(pixelFormat);
|
||||
if (!tenBit && blitSource && storage == DisplayTexelFormat(pixelFormat)) return ResidentPresent::Blit;
|
||||
switch (storage) {
|
||||
case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
|
||||
case VK_FORMAT_A2R10G10B10_UNORM_PACK32:
|
||||
return tenBit ? ResidentPresent::Convert : ResidentPresent::None;
|
||||
case VK_FORMAT_R8G8B8A8_UNORM:
|
||||
case VK_FORMAT_R8G8B8A8_SRGB:
|
||||
case VK_FORMAT_B8G8R8A8_UNORM:
|
||||
case VK_FORMAT_B8G8R8A8_SRGB:
|
||||
return tenBit ? ResidentPresent::None : ResidentPresent::Convert;
|
||||
default:
|
||||
return ResidentPresent::None;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PresentationScaler.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/DisplayFormat.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/GpuColorTransfer.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp"
|
||||
|
|
@ -1688,33 +1689,26 @@ void WriteFrameBmp(int index, std::uint32_t fullWidth, std::uint32_t fullHeight,
|
|||
}
|
||||
}
|
||||
|
||||
// Whether a resident image of the display buffer can be blitted to the swapchain as it is: one 2D
|
||||
// level of the display's extent in a normalized 4-byte color format (a blit converts components,
|
||||
// so an integer or float reinterpretation would present wrong values). `filter` gets the best
|
||||
// filter the format allows.
|
||||
bool ResidentPresentable(const Graphics::Context& context, const Graphics::StorageTexture& image, const DisplayBuffer& buffer, VkFilter& filter) {
|
||||
ResidentPresent ResidentPresentable(const Graphics::Context& context, const Graphics::StorageTexture& image, const DisplayBuffer& buffer, VkFilter& filter) {
|
||||
const auto& descriptor = image.Descriptor();
|
||||
if (descriptor.width != buffer.width || descriptor.height != buffer.height || descriptor.mipCount != 1 || image.ImageLayers() != 1 || image.ImageDepth() != 1) return false;
|
||||
if (image.GuestBytes() != DisplayBufferSize(buffer)) return false;
|
||||
if (descriptor.width != buffer.width || descriptor.height != buffer.height || descriptor.mipCount != 1 || image.ImageLayers() != 1 || image.ImageDepth() != 1) return ResidentPresent::None;
|
||||
if (image.GuestBytes() != DisplayBufferSize(buffer)) return ResidentPresent::None;
|
||||
const auto format = Graphics::StorageFormatForGuest(context, descriptor.format);
|
||||
constexpr std::uint64_t tenBitFormat = 0x0100000000000000ull;
|
||||
const bool tenBit = (buffer.pixelFormat & tenBitFormat) != 0;
|
||||
const bool rgba = (buffer.pixelFormat & ~tenBitFormat) == 0x8000000022000000ull;
|
||||
const auto displayFormat = tenBit ? VK_FORMAT_A2B10G10R10_UNORM_PACK32 : rgba ? VK_FORMAT_R8G8B8A8_UNORM : VK_FORMAT_B8G8R8A8_UNORM;
|
||||
if (format != displayFormat) return false;
|
||||
VkFormatProperties properties{};
|
||||
context.formatProperties(context.physical, format, &properties);
|
||||
if ((properties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) == 0) return false;
|
||||
const bool blitSource = (properties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) != 0;
|
||||
filter = (properties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0 ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
|
||||
return true;
|
||||
return ResidentPresentPath(format, buffer.pixelFormat, blitSource);
|
||||
}
|
||||
|
||||
// The resident image a display buffer is blitted from, or null; `pending` tells a buffer without
|
||||
// a pending image from one whose image is unsuitable.
|
||||
std::shared_ptr<Graphics::StorageTexture> PresentableResident(const Graphics::Context& context, const DisplayBuffer& buffer, VkFilter& filter, bool& pending) {
|
||||
std::shared_ptr<Graphics::StorageTexture> PresentableResident(const Graphics::Context& context, const DisplayBuffer& buffer, VkFilter& filter, bool& pending, bool& convert) {
|
||||
auto resident = Graphics::StorageTexture::FindPending(buffer.address, DisplayBufferSize(buffer));
|
||||
pending = resident != nullptr;
|
||||
if (resident != nullptr && !ResidentPresentable(context, *resident, buffer, filter)) resident.reset();
|
||||
convert = false;
|
||||
if (resident == nullptr) return resident;
|
||||
const auto path = ResidentPresentable(context, *resident, buffer, filter);
|
||||
if (path == ResidentPresent::None) resident.reset();
|
||||
convert = path == ResidentPresent::Convert;
|
||||
return resident;
|
||||
}
|
||||
|
||||
|
|
@ -1763,9 +1757,10 @@ bool VulkanDevice::PresentDisplayBuffer(const DisplayBuffer& buffer) {
|
|||
const auto bytes = DisplayBufferSize(buffer);
|
||||
std::shared_ptr<Graphics::StorageTexture> resident;
|
||||
VkFilter filter = VK_FILTER_LINEAR;
|
||||
bool convert = false;
|
||||
if (!NoResidentPresent()) {
|
||||
bool pending = false;
|
||||
resident = PresentableResident(graphicsContext(), buffer, filter, pending);
|
||||
resident = PresentableResident(graphicsContext(), buffer, filter, pending, convert);
|
||||
if (!pending) {
|
||||
++notPending;
|
||||
} else if (resident == nullptr) {
|
||||
|
|
@ -1801,7 +1796,7 @@ bool VulkanDevice::PresentDisplayBuffer(const DisplayBuffer& buffer) {
|
|||
std::fprintf(stderr, "[flip] %llu presents from the resident image (%llu refreshed first), through guest memory: %llu not pending, %llu unsuitable; %llu GPU frame dumps\n", static_cast<unsigned long long>(residentPresents), static_cast<unsigned long long>(refreshedPresents), static_cast<unsigned long long>(notPending), static_cast<unsigned long long>(unsuitable), static_cast<unsigned long long>(gpuDumps));
|
||||
}
|
||||
CaptureTrace::Log("present dump=%d address=%llx width=%u height=%u resident=%d generation=%llu", dumpFrame ? state->nextDumpIndex : -1, static_cast<unsigned long long>(buffer.address), buffer.width, buffer.height, resident != nullptr, static_cast<unsigned long long>(resident ? resident->Generation() : 0));
|
||||
if (!present(buffer.width, buffer.height, true, {}, &buffer, resident, filter, dumpFrame)) {
|
||||
if (!present(buffer.width, buffer.height, true, {}, &buffer, resident, filter, dumpFrame, convert)) {
|
||||
// A dropped frame (swapchain out of date) keeps the dump numbering contiguous.
|
||||
if (dumpFrame) --dumps.dumped;
|
||||
return false;
|
||||
|
|
@ -1846,7 +1841,7 @@ bool VulkanDevice::AcquireImage() {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaque, std::span<const std::byte> pixels, const DisplayBuffer* display, const std::shared_ptr<Graphics::StorageTexture>& resident, VkFilter residentFilter, bool dumpFrame) {
|
||||
bool VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaque, std::span<const std::byte> pixels, const DisplayBuffer* display, const std::shared_ptr<Graphics::StorageTexture>& resident, VkFilter residentFilter, bool dumpFrame, bool residentConvert) {
|
||||
PerformanceTimer timing("Vulkan.Present");
|
||||
APS5_LOG_OUT_DEBUG("present begin width=%u height=%u opaque=%u pixels=%zu", width, height, static_cast<unsigned>(opaque), pixels.size());
|
||||
require(state->swapchain != VK_NULL_HANDLE, "device has no swapchain");
|
||||
|
|
@ -1861,7 +1856,8 @@ bool VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaqu
|
|||
require(index < state->images.size() && index < state->rendered.size(), "acquired image index is out of range");
|
||||
auto& rendered = state->rendered[index];
|
||||
const bool clearOnly = pixels.empty() && display == nullptr;
|
||||
const bool direct = resident != nullptr;
|
||||
require(!residentConvert || (resident != nullptr && display != nullptr), "a converted resident presentation needs its image and display buffer");
|
||||
const bool direct = resident != nullptr && !residentConvert;
|
||||
// The scaler's source image, the color transfer's staging and the upload buffer are single
|
||||
// objects an in-flight blit through them may still read: the paths using or re-creating them
|
||||
// wait for every slot first. The game path did so before taking the mutex
|
||||
|
|
@ -1942,14 +1938,13 @@ bool VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaqu
|
|||
residentBarrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS};
|
||||
pipelineBarrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &residentBarrier);
|
||||
Graphics::Recorder::CountBarriers(CommandClass::PresentBlit);
|
||||
if (!direct) state->scaler->RecordBlitInto(commands, resident->Image(), VK_IMAGE_LAYOUT_GENERAL, residentFilter);
|
||||
if (residentConvert) {
|
||||
state->colorTransfer->DetileImage(commands, resident->Image(), VK_IMAGE_LAYOUT_GENERAL, width, height, Graphics::ColorTileMode::RenderTarget, DisplayRedLow(display->pixelFormat), DisplayTenBit(display->pixelFormat));
|
||||
state->scaler->RecordUpload(commands, state->colorTransfer->LinearBuffer());
|
||||
}
|
||||
} else {
|
||||
if (display != nullptr) {
|
||||
// Bit 56 selects the 10-bit A2B10G10R10 variant; R8G8B8A8-ordered formats need red and blue
|
||||
// swapped for the B8G8R8A8 swapchain.
|
||||
constexpr std::uint64_t tenBitFormat = 0x0100000000000000ull;
|
||||
const bool tenBit = (display->pixelFormat & tenBitFormat) != 0;
|
||||
state->colorTransfer->Detile(commands, (display->pixelFormat & ~tenBitFormat) == 0x8000000022000000ull, tenBit);
|
||||
state->colorTransfer->Detile(commands, DisplayRedLow(display->pixelFormat), DisplayTenBit(display->pixelFormat));
|
||||
}
|
||||
state->scaler->RecordUpload(commands, display != nullptr ? state->colorTransfer->LinearBuffer() : state->uploadBuffer);
|
||||
}
|
||||
|
|
@ -2076,7 +2071,8 @@ bool VulkanDevice::PresentWaitsForSlots(const DisplayBuffer* buffer) const {
|
|||
if (NoResidentPresent()) return true;
|
||||
VkFilter filter = VK_FILTER_LINEAR;
|
||||
bool pending = false;
|
||||
if (PresentableResident(graphicsContext(), *buffer, filter, pending) == nullptr) return true;
|
||||
bool convert = false;
|
||||
if (PresentableResident(graphicsContext(), *buffer, filter, pending, convert) == nullptr || convert) return true;
|
||||
return state->scaler == nullptr || state->scaler->SourceWidth() != buffer->width || state->scaler->SourceHeight() != buffer->height;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ public:
|
|||
GpuColorTransfer& operator=(const GpuColorTransfer&) = delete;
|
||||
void Upload(std::uint64_t address, std::uint32_t width, std::uint32_t height, ColorTileMode mode);
|
||||
void Detile(VkCommandBuffer commands, bool swapRedBlue = false, bool tenBit = false);
|
||||
void DetileImage(VkCommandBuffer commands, VkImage image, VkImageLayout layout, std::uint32_t width, std::uint32_t height, ColorTileMode mode, bool swapRedBlue, bool tenBit);
|
||||
void Tile(VkCommandBuffer commands);
|
||||
void WriteBack(std::uint64_t address);
|
||||
VkBuffer LinearBuffer() const;
|
||||
|
|
@ -22,6 +23,7 @@ public:
|
|||
private:
|
||||
void prepare(std::uint32_t width, std::uint32_t height, ColorTileMode mode);
|
||||
void convert(VkCommandBuffer commands, bool toTiled, bool swapRedBlue, bool tenBit = false);
|
||||
void dispatch(VkCommandBuffer commands, bool toTiled, bool swapRedBlue, bool tenBit, bool tiledSource);
|
||||
void release() noexcept;
|
||||
Context context;
|
||||
VkDescriptorSetLayout descriptorLayout = VK_NULL_HANDLE;
|
||||
|
|
|
|||
|
|
@ -104,12 +104,20 @@ void GpuColorTransfer::convert(VkCommandBuffer commands, bool toTiled, bool swap
|
|||
// The guest bytes move to video memory with one DMA copy; the shader's scattered accesses stay local.
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
CopyBuffer(context, commands, tiled->Handle(), 0, tiledDevice->Handle(), 0, tiledDevice->Size());
|
||||
dispatch(commands, toTiled, swapRedBlue, tenBit, mode == ColorTileMode::RenderTarget);
|
||||
if (toTiled) {
|
||||
CopyBuffer(context, commands, tiledDevice->Handle(), 0, tiled->Handle(), 0, tiledDevice->Size());
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
void GpuColorTransfer::dispatch(VkCommandBuffer commands, bool toTiled, bool swapRedBlue, bool tenBit, bool tiledSource) {
|
||||
VkMemoryBarrier before{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
before.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
|
||||
before.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
|
||||
const auto barrier = context.Function<PFN_vkCmdPipelineBarrier>("vkCmdPipelineBarrier");
|
||||
barrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 1, &before, 0, nullptr, 0, nullptr);
|
||||
const std::array<std::uint32_t, 4> push{width, height, (width + 127u) / 128u, (toTiled ? 1u : 0u) | (swapRedBlue ? 2u : 0u) | (mode == ColorTileMode::RenderTarget ? 4u : 0u) | (tenBit ? 8u : 0u)};
|
||||
const std::array<std::uint32_t, 4> push{width, height, (width + 127u) / 128u, (toTiled ? 1u : 0u) | (swapRedBlue ? 2u : 0u) | (tiledSource ? 4u : 0u) | (tenBit ? 8u : 0u)};
|
||||
context.Function<PFN_vkCmdBindPipeline>("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
|
||||
context.Function<PFN_vkCmdBindDescriptorSets>("vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
|
||||
context.Function<PFN_vkCmdPushConstants>("vkCmdPushConstants")(commands, pipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(push), push.data());
|
||||
|
|
@ -118,10 +126,18 @@ void GpuColorTransfer::convert(VkCommandBuffer commands, bool toTiled, bool swap
|
|||
after.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
|
||||
after.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
barrier(commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &after, 0, nullptr, 0, nullptr);
|
||||
if (toTiled) {
|
||||
CopyBuffer(context, commands, tiledDevice->Handle(), 0, tiled->Handle(), 0, tiledDevice->Size());
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
void GpuColorTransfer::DetileImage(VkCommandBuffer commands, VkImage image, VkImageLayout layout, std::uint32_t newWidth, std::uint32_t newHeight, ColorTileMode newMode, bool swapRedBlue, bool tenBit) {
|
||||
Require(commands != VK_NULL_HANDLE && image != VK_NULL_HANDLE, "color transfer image source is unavailable");
|
||||
prepare(newWidth, newHeight, newMode);
|
||||
Require(tiledDevice->Size() >= static_cast<VkDeviceSize>(width) * height * 4u, "color transfer buffer is smaller than the image");
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
VkBufferImageCopy copy{};
|
||||
copy.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
||||
copy.imageExtent = {width, height, 1};
|
||||
context.Function<PFN_vkCmdCopyImageToBuffer>("vkCmdCopyImageToBuffer")(commands, image, layout, tiledDevice->Handle(), 1, ©);
|
||||
dispatch(commands, false, swapRedBlue, tenBit, false);
|
||||
}
|
||||
|
||||
void GpuColorTransfer::Detile(VkCommandBuffer commands, bool swapRedBlue, bool tenBit) {
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ std::optional<std::uint32_t> FindGuestColorTargetFormat(VkFormat format, std::ui
|
|||
switch (format) {
|
||||
case VK_FORMAT_B8G8R8A8_UNORM: format = VK_FORMAT_R8G8B8A8_UNORM; break;
|
||||
case VK_FORMAT_B8G8R8A8_SRGB: format = VK_FORMAT_R8G8B8A8_SRGB; break;
|
||||
case VK_FORMAT_A2R10G10B10_UNORM_PACK32: format = VK_FORMAT_A2B10G10R10_UNORM_PACK32; break;
|
||||
default: break;
|
||||
}
|
||||
return FindGuestTextureFormat(format, elementBytes);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
#include "prx/libc/include/GuestArena.hpp"
|
||||
#include "prx/libc/include/GuestWriteWatch.hpp"
|
||||
#include "ResidentPresent.hpp"
|
||||
#include "SampleLod_spv.h"
|
||||
#include <SDL_loadso.h>
|
||||
#ifdef _WIN32
|
||||
|
|
@ -2288,6 +2289,7 @@ int main() {
|
|||
drawSnapshotReuseTests(device, recorder);
|
||||
drawSnapshotEvictionTests(device);
|
||||
drawInputReuseTests(device, recorder);
|
||||
RunResidentPresentTests(device.GetContext());
|
||||
storeRunTests(device, recorder);
|
||||
movedMetadataTests(device, recorder);
|
||||
keysFillTests(device, recorder);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,159 @@
|
|||
#include "ResidentPresent.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/DisplayFormat.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/GpuColorTransfer.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace AgcDriver::Graphics;
|
||||
using AgcDriver::DisplayBuffer;
|
||||
using AgcDriver::ResidentPresent;
|
||||
|
||||
constexpr std::uint64_t Bgra8 = 0x8000000000000000ull;
|
||||
constexpr std::uint64_t Rgba8 = 0x8000000022000000ull;
|
||||
constexpr std::uint64_t TenBit = 0x0100000000000000ull;
|
||||
|
||||
void decisionTests() {
|
||||
using AgcDriver::DisplayTexelFormat;
|
||||
using AgcDriver::ResidentPresentPath;
|
||||
Require(DisplayTexelFormat(Bgra8) == VK_FORMAT_B8G8R8A8_UNORM && DisplayTexelFormat(Rgba8) == VK_FORMAT_R8G8B8A8_UNORM, "8-bit display texel formats are wrong");
|
||||
Require(DisplayTexelFormat(Bgra8 | TenBit) == VK_FORMAT_A2R10G10B10_UNORM_PACK32 && DisplayTexelFormat(Rgba8 | TenBit) == VK_FORMAT_A2B10G10R10_UNORM_PACK32, "10-bit display texel formats are wrong");
|
||||
Require(ResidentPresentPath(VK_FORMAT_B8G8R8A8_UNORM, Bgra8, true) == ResidentPresent::Blit && ResidentPresentPath(VK_FORMAT_R8G8B8A8_UNORM, Rgba8, true) == ResidentPresent::Blit, "an 8-bit image in its display's order is not blitted");
|
||||
Require(ResidentPresentPath(VK_FORMAT_B8G8R8A8_UNORM, Bgra8, false) == ResidentPresent::Convert, "an 8-bit image that cannot be a blit source is not converted");
|
||||
Require(ResidentPresentPath(VK_FORMAT_R8G8B8A8_UNORM, Bgra8, true) == ResidentPresent::Convert && ResidentPresentPath(VK_FORMAT_B8G8R8A8_UNORM, Rgba8, true) == ResidentPresent::Convert, "an 8-bit image in the other order is not converted");
|
||||
Require(ResidentPresentPath(VK_FORMAT_R8G8B8A8_SRGB, Rgba8, true) == ResidentPresent::Convert, "an sRGB image is blitted (the blit would decode it)");
|
||||
for (const auto display : {Bgra8 | TenBit, Rgba8 | TenBit}) {
|
||||
for (const auto storage : {VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_FORMAT_A2R10G10B10_UNORM_PACK32}) {
|
||||
Require(ResidentPresentPath(storage, display, true) == ResidentPresent::Convert, "a 10-bit image is blitted (the blit rounds where the guest path truncates)");
|
||||
}
|
||||
Require(ResidentPresentPath(VK_FORMAT_B8G8R8A8_UNORM, display, true) == ResidentPresent::None, "an 8-bit image presents a 10-bit display");
|
||||
}
|
||||
Require(ResidentPresentPath(VK_FORMAT_A2B10G10R10_UNORM_PACK32, Bgra8, true) == ResidentPresent::None && ResidentPresentPath(VK_FORMAT_R16G16_SFLOAT, Bgra8, true) == ResidentPresent::None, "an image of another type presents an 8-bit display");
|
||||
const auto guest = FindGuestColorTargetFormat(VK_FORMAT_A2R10G10B10_UNORM_PACK32, 4);
|
||||
Require(guest.has_value() && guest == FindGuestTextureFormat(VK_FORMAT_A2B10G10R10_UNORM_PACK32, 4), "the blue-low 10-bit color target has no storage format");
|
||||
}
|
||||
|
||||
std::uint32_t memoryType(const Context& context, std::uint32_t bits, VkMemoryPropertyFlags flags) {
|
||||
for (std::uint32_t i = 0; i < context.memory.memoryTypeCount; ++i) {
|
||||
if ((bits & (1u << i)) != 0 && (context.memory.memoryTypes[i].propertyFlags & flags) == flags) return i;
|
||||
}
|
||||
throw std::runtime_error("no device-local memory type for the test image");
|
||||
}
|
||||
|
||||
void conversionTest(const Context& context, std::uint64_t pixelFormat, VkFormat format) {
|
||||
constexpr std::uint32_t width = 200;
|
||||
constexpr std::uint32_t height = 130;
|
||||
std::vector<std::byte> words(static_cast<std::size_t>(width) * height * 4);
|
||||
std::uint32_t seed = 0x9e3779b9u ^ static_cast<std::uint32_t>(format);
|
||||
for (std::size_t i = 0; i < words.size(); ++i) {
|
||||
seed = seed * 1664525u + 1013904223u;
|
||||
words[i] = static_cast<std::byte>(seed >> 24u);
|
||||
}
|
||||
const ColorTargetLayout layout(width, height, ColorTileMode::RenderTarget);
|
||||
std::vector<std::byte> storage(layout.Bytes() + 65536);
|
||||
auto* aligned = reinterpret_cast<std::byte*>((reinterpret_cast<std::uintptr_t>(storage.data()) + 65535u) & ~std::uintptr_t{65535});
|
||||
std::span<std::byte> guest(aligned, layout.Bytes());
|
||||
layout.Tile(words, guest);
|
||||
const DisplayBuffer display{reinterpret_cast<std::uint64_t>(aligned), pixelFormat, width, height};
|
||||
Require(AgcDriver::DisplayBufferSize(display) == guest.size(), "test display buffer size differs from the color layout");
|
||||
const auto expected = AgcDriver::DecodeDisplayBuffer(display, guest);
|
||||
|
||||
VkImageCreateInfo imageInfo{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
||||
imageInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
imageInfo.format = format;
|
||||
imageInfo.extent = {width, height, 1};
|
||||
imageInfo.mipLevels = 1;
|
||||
imageInfo.arrayLayers = 1;
|
||||
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
Check(context.Function<PFN_vkCreateImage>("vkCreateImage")(context.device, &imageInfo, nullptr, &image), "vkCreateImage resident present test");
|
||||
struct Release {
|
||||
const Context& context;
|
||||
VkImage& image;
|
||||
VkDeviceMemory& memory;
|
||||
~Release() {
|
||||
if (image) context.Function<PFN_vkDestroyImage>("vkDestroyImage")(context.device, image, nullptr);
|
||||
if (memory) context.Function<PFN_vkFreeMemory>("vkFreeMemory")(context.device, memory, nullptr);
|
||||
}
|
||||
} release{context, image, memory};
|
||||
VkMemoryRequirements requirements{};
|
||||
context.Function<PFN_vkGetImageMemoryRequirements>("vkGetImageMemoryRequirements")(context.device, image, &requirements);
|
||||
VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
||||
allocation.allocationSize = requirements.size;
|
||||
allocation.memoryTypeIndex = memoryType(context, requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
Check(context.Function<PFN_vkAllocateMemory>("vkAllocateMemory")(context.device, &allocation, nullptr, &memory), "vkAllocateMemory resident present test");
|
||||
Check(context.Function<PFN_vkBindImageMemory>("vkBindImageMemory")(context.device, image, memory, 0), "vkBindImageMemory resident present test");
|
||||
|
||||
Buffer upload(context, words.size(), VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
|
||||
std::memcpy(upload.Bytes().data(), words.data(), words.size());
|
||||
Buffer fromImage(context, expected.size(), VK_BUFFER_USAGE_TRANSFER_DST_BIT);
|
||||
Buffer fromGuest(context, expected.size(), VK_BUFFER_USAGE_TRANSFER_DST_BIT);
|
||||
GpuColorTransfer transfer(context);
|
||||
const bool redLow = AgcDriver::DisplayRedLow(pixelFormat);
|
||||
const bool tenBit = AgcDriver::DisplayTenBit(pixelFormat);
|
||||
const auto barrier = context.Function<PFN_vkCmdPipelineBarrier>("vkCmdPipelineBarrier");
|
||||
const auto readback = [&](VkCommandBuffer commands, Buffer& destination) {
|
||||
const VkBufferCopy copy{0, 0, expected.size()};
|
||||
context.Function<PFN_vkCmdCopyBuffer>("vkCmdCopyBuffer")(commands, transfer.LinearBuffer(), destination.Handle(), 1, ©);
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_HOST_READ_BIT);
|
||||
};
|
||||
transfer.Upload(display.address, width, height, ColorTileMode::RenderTarget);
|
||||
{
|
||||
CommandBatch batch(context);
|
||||
const auto commands = batch.Handle();
|
||||
VkImageMemoryBarrier toTransfer{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||||
toTransfer.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
toTransfer.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
toTransfer.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
toTransfer.srcQueueFamilyIndex = toTransfer.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
toTransfer.image = image;
|
||||
toTransfer.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
barrier(commands, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toTransfer);
|
||||
VkBufferImageCopy region{};
|
||||
region.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
||||
region.imageExtent = {width, height, 1};
|
||||
context.Function<PFN_vkCmdCopyBufferToImage>("vkCmdCopyBufferToImage")(commands, upload.Handle(), image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
||||
auto toGeneral = toTransfer;
|
||||
toGeneral.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
toGeneral.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
toGeneral.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
toGeneral.newLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
barrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toGeneral);
|
||||
transfer.Detile(commands, redLow, tenBit);
|
||||
readback(commands, fromGuest);
|
||||
transfer.DetileImage(commands, image, VK_IMAGE_LAYOUT_GENERAL, width, height, ColorTileMode::RenderTarget, redLow, tenBit);
|
||||
readback(commands, fromImage);
|
||||
batch.SubmitAndWait();
|
||||
}
|
||||
const auto name = std::string(tenBit ? "10-bit " : "8-bit ") + (redLow ? "red-low" : "blue-low") + " display from VkFormat " + std::to_string(static_cast<int>(format));
|
||||
Require(std::equal(expected.begin(), expected.end(), fromGuest.Bytes().begin()), "the GPU detile differs from the CPU decode: " + name);
|
||||
Require(std::equal(expected.begin(), expected.end(), fromImage.Bytes().begin()), "the resident image presents other pixels than guest memory: " + name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void RunResidentPresentTests(const Context& context) {
|
||||
decisionTests();
|
||||
conversionTest(context, Bgra8 | TenBit, VK_FORMAT_A2B10G10R10_UNORM_PACK32);
|
||||
conversionTest(context, Rgba8 | TenBit, VK_FORMAT_A2B10G10R10_UNORM_PACK32);
|
||||
conversionTest(context, Bgra8 | TenBit, VK_FORMAT_A2R10G10B10_UNORM_PACK32);
|
||||
conversionTest(context, Bgra8, VK_FORMAT_R8G8B8A8_UNORM);
|
||||
conversionTest(context, Rgba8, VK_FORMAT_B8G8R8A8_UNORM);
|
||||
std::cout << "resident present format and conversion tests passed\n";
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_RESIDENTPRESENT_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_RESIDENTPRESENT_HPP
|
||||
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Context.hpp"
|
||||
|
||||
void RunResidentPresentTests(const AgcDriver::Graphics::Context& context);
|
||||
|
||||
#endif
|
||||
Loading…
Reference in New Issue