mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-03 12:57:27 +00:00
Merge pull request #6892 from tibzejoker/perf/resident-linear-targets
perf(agc): linear color targets over imported memory get a resident image
This commit is contained in:
5 files changed
+286
-2
No files matched your search
@@ -66,6 +66,7 @@ void SetImportWatch(const Context& context, ImportWatch watch);
|
||||
// (alignment and budget permitting), or null. Bytes at `address` are at `address - import->base` in
|
||||
// the import's buffer.
|
||||
const HostImport* HostImportFor(const Context& context, std::uint64_t address, std::size_t bytes);
|
||||
const HostImport* ExistingHostImportFor(const Context& context, std::uint64_t address, std::size_t bytes);
|
||||
void SetHostImportWindow(std::uint64_t bytes);
|
||||
// Tests: the window of one large registered range (64 MiB) and the import budget (0: the configured one).
|
||||
void SetHostImportRangeWindow(std::uint64_t bytes);
|
||||
|
||||
@@ -1554,6 +1554,10 @@ void keepRecordedDraw(Recorder& recorder, const std::shared_ptr<ShaderResources>
|
||||
if (index < slices.size()) residents[index]->MarkSliceDirty(slices[index]);
|
||||
else residents[index]->MarkDirty();
|
||||
}
|
||||
for (const auto& resident : residents) {
|
||||
if (resident == nullptr || resident->Descriptor().tileMode != TextureTileMode::kLinear) continue;
|
||||
StorageTexture::FlushPending(resident->Descriptor().baseAddress, static_cast<std::size_t>(resident->GuestBytes()), nullptr, "linear target");
|
||||
}
|
||||
}
|
||||
|
||||
// What recordDraw records from: the recorder, the resources, the pipeline and framebuffer, the
|
||||
@@ -2010,7 +2014,9 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
|
||||
timer.phase(PhaseSetup);
|
||||
// Debug aid: APS5_NO_RESIDENT_TARGETS=1 copies every target in and out again.
|
||||
static const bool residentTargets = std::getenv("APS5_NO_RESIDENT_TARGETS") == nullptr;
|
||||
if ((binding.gpuTiling || (color.tileMode != ColorTileMode::Linear && context.detiler != nullptr)) && residentTargets) {
|
||||
static const bool residentLinear = std::getenv("APS5_NO_RESIDENT_LINEAR") == nullptr;
|
||||
const bool linearResident = residentLinear && color.tileMode == ColorTileMode::Linear && ExistingHostImportFor(context, color.address, colorLayout.Bytes()) != nullptr;
|
||||
if ((binding.gpuTiling || ((color.tileMode != ColorTileMode::Linear || linearResident) && context.detiler != nullptr)) && residentTargets) {
|
||||
// The lookup refreshes the image on every draw (StorageTexture::Refresh: FlushPending,
|
||||
// CollectWrites over the target's pages, the DCC key scan of TextureClearKeys, then
|
||||
// UnchangedSince). The page walk is skipped while the worker's collect epoch lasts
|
||||
|
||||
@@ -2613,6 +2613,14 @@ const HostImport* HostImportFor(const Context& context, std::uint64_t address, s
|
||||
return importAccess(context, state, address, address + bytes, lease);
|
||||
}
|
||||
|
||||
const HostImport* ExistingHostImportFor(const Context& context, std::uint64_t address, std::size_t bytes) {
|
||||
if (context.hostImportAlignment == 0 || bytes == 0 || bytes > std::numeric_limits<std::uint64_t>::max() - address) return nullptr;
|
||||
auto& state = Imports();
|
||||
std::lock_guard lock(state.mutex);
|
||||
if (importsStale(context, state)) return nullptr;
|
||||
return findImport(state, address, address + bytes);
|
||||
}
|
||||
|
||||
bool ImportMappedRanges(const Context& context, const GuestAllocations::Mapped& ranges, std::uint64_t generation, bool adoptDevice) {
|
||||
if (context.hostImportAlignment == 0) return true;
|
||||
ensurePinWaiter();
|
||||
|
||||
@@ -1399,7 +1399,8 @@ std::uint32_t GuestFormatFor(VkFormat format, std::uint32_t elementBytes) {
|
||||
}
|
||||
|
||||
GuestTextureResource SurfaceForTarget(const ColorTarget& color) {
|
||||
Require(color.tileMode != ColorTileMode::Linear, "linear color targets are not resident");
|
||||
static const bool residentLinear = std::getenv("APS5_NO_RESIDENT_LINEAR") == nullptr;
|
||||
Require(residentLinear || color.tileMode != ColorTileMode::Linear, "linear color targets are not resident");
|
||||
const bool chain = color.mipCount > 1;
|
||||
GuestTextureResource surface{};
|
||||
surface.baseAddress = chain ? color.surfaceAddress : color.address;
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/State.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
|
||||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
#include "prx/libc/include/GuestArena.hpp"
|
||||
#include "prx/libc/include/GuestWriteWatch.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace AgcDriver {
|
||||
|
||||
struct VulkanDeviceTestAccess {
|
||||
static Graphics::Context ContextOf(const VulkanDevice& device) { return device.graphicsContext(); }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Width = 64;
|
||||
constexpr std::uint32_t Height = 16;
|
||||
constexpr std::size_t BlockBytes = 65536;
|
||||
constexpr std::uint32_t Rgba32FloatInfo = 0x8738u;
|
||||
constexpr std::uint32_t LinearAttrib3 = 0x09000000u;
|
||||
constexpr std::uint32_t Float32AbgrExport = 9;
|
||||
constexpr std::uint32_t NoInput = 0xffffffffu;
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 6> VertexCode{
|
||||
0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000,
|
||||
};
|
||||
|
||||
constexpr std::array<std::array<float, 4>, 3> Triangle{{
|
||||
{-1.0f, -1.0f, 0.5f, 1.0f}, {3.0f, -1.0f, 0.5f, 1.0f}, {-1.0f, 3.0f, 0.5f, 1.0f}
|
||||
}};
|
||||
|
||||
alignas(256) std::array<std::array<std::uint32_t, 16>, 4> Programs{};
|
||||
std::size_t programCount = 0;
|
||||
|
||||
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(data);
|
||||
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu};
|
||||
}
|
||||
|
||||
std::span<const std::uint32_t> Program(std::uint32_t fixedVgpr, std::uint32_t stippleVgpr) {
|
||||
Require(programCount < Programs.size(), "too many test programs");
|
||||
auto& code = Programs[programCount++];
|
||||
std::size_t at = 0;
|
||||
code[at++] = 0x36000000u | (20u << 17u) | (fixedVgpr << 9u) | 0xffu;
|
||||
code[at++] = 0x0000ffffu;
|
||||
code[at++] = 0x2c000000u | (21u << 17u) | (fixedVgpr << 9u) | 0x90u;
|
||||
code[at++] = 0x7e000c00u | (20u << 17u) | (256u + 20u);
|
||||
code[at++] = 0x7e000c00u | (21u << 17u) | (256u + 21u);
|
||||
code[at++] = stippleVgpr == NoInput ? 0x7e000280u | (22u << 17u) : 0x7e000c00u | (22u << 17u) | (256u + stippleVgpr);
|
||||
code[at++] = 0x7e0002f2u | (23u << 17u);
|
||||
code[at++] = 0xf800180fu;
|
||||
code[at++] = 0x17161514u;
|
||||
code[at++] = 0xbf810000u;
|
||||
return std::span<const std::uint32_t>(code.data(), at);
|
||||
}
|
||||
|
||||
struct Block {
|
||||
explicit Block(std::size_t bytes) : bytes(bytes), watched(AgcDriver::GuestMemory::WriteWatched()) {
|
||||
#ifdef _WIN32
|
||||
if (watched) {
|
||||
data = static_cast<std::uint8_t*>(GuestArena::GuestArenaAllocate_nid_postfix(bytes, BlockBytes));
|
||||
GuestArena::GuestArenaCommit_nid_postfix(data, bytes, PAGE_READWRITE, bytes);
|
||||
} else {
|
||||
data = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
|
||||
}
|
||||
#else
|
||||
if (watched) {
|
||||
void* raw = mmap(nullptr, bytes + BlockBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
Require(raw != MAP_FAILED, "cannot map the color target");
|
||||
const auto begin = reinterpret_cast<std::uintptr_t>(raw);
|
||||
const auto aligned = (begin + BlockBytes - 1) & ~(static_cast<std::uintptr_t>(BlockBytes) - 1);
|
||||
if (aligned != begin) munmap(raw, aligned - begin);
|
||||
if (aligned + bytes != begin + bytes + BlockBytes) munmap(reinterpret_cast<void*>(aligned + bytes), begin + BlockBytes - aligned);
|
||||
data = reinterpret_cast<std::uint8_t*>(aligned);
|
||||
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(data, bytes);
|
||||
} else {
|
||||
data = static_cast<std::uint8_t*>(std::aligned_alloc(BlockBytes, bytes));
|
||||
}
|
||||
#endif
|
||||
Require(data != nullptr, "cannot allocate the color target");
|
||||
Require(!watched || AgcDriver::GuestMemory::Watched(Address(), bytes), "the color target is not write-watched");
|
||||
GuestAllocations::Mutation mutation;
|
||||
mutation.Add(data, bytes, true, true, true);
|
||||
}
|
||||
~Block() {
|
||||
AgcDriver::Graphics::StorageTexture::FlushPending(Address(), bytes, nullptr, "test release");
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
mutation.Remove(data);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
if (watched) {
|
||||
GuestArena::GuestArenaReset_nid_postfix(data, bytes);
|
||||
GuestArena::GuestArenaRelease_nid_postfix(data, bytes);
|
||||
} else {
|
||||
VirtualFree(data, 0, MEM_RELEASE);
|
||||
}
|
||||
#else
|
||||
if (watched) {
|
||||
munmap(data, bytes);
|
||||
GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(data, bytes);
|
||||
} else {
|
||||
std::free(data);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
Block(const Block&) = delete;
|
||||
Block& operator=(const Block&) = delete;
|
||||
std::uint64_t Address() const { return reinterpret_cast<std::uintptr_t>(data); }
|
||||
std::size_t bytes;
|
||||
bool watched;
|
||||
std::uint8_t* data = nullptr;
|
||||
};
|
||||
|
||||
AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::uint32_t info) {
|
||||
AgcDriver::Registers cx;
|
||||
cx[0x318] = static_cast<std::uint32_t>(block.Address() >> 8u);
|
||||
cx[0x31b] = 0;
|
||||
cx[0x31c] = info;
|
||||
cx[0x31d] = 0;
|
||||
cx[0x323] = 0;
|
||||
cx[0x324] = 0;
|
||||
cx[0x3b0] = ((Width - 1u) << 14u) | (Height - 1u);
|
||||
cx[0x3b8] = LinearAttrib3;
|
||||
cx[0x390] = static_cast<std::uint32_t>(block.Address() >> 40u);
|
||||
auto color = AgcDriver::Graphics::DecodeColorBuffer(cx, 0);
|
||||
color.exportIndex = 0;
|
||||
return color;
|
||||
}
|
||||
|
||||
void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, std::uint32_t inputAddr, std::span<const std::uint32_t> pixelCode) {
|
||||
const auto target = device.Target();
|
||||
constexpr std::uint32_t waveSize = 64;
|
||||
std::vector<std::uint32_t> vertexUserData(4, 0u);
|
||||
const auto vertexBuffer = BufferDescriptor(Triangle.data(), 16u, static_cast<std::uint32_t>(Triangle.size()));
|
||||
std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin());
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> vertexMemory{{{reinterpret_cast<std::uintptr_t>(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}};
|
||||
ShaderRecompiler::RecompileRequest vertex{
|
||||
{ShaderStage::Vertex, reinterpret_cast<std::uintptr_t>(VertexCode.data()), VertexCode, 0, {}},
|
||||
{waveSize, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory},
|
||||
target,
|
||||
{0, 0, 0, 64}
|
||||
};
|
||||
vertex.useCache = false;
|
||||
const auto vertexResult = ShaderRecompiler::Recompile(vertex);
|
||||
const auto vertexPush = static_cast<std::uint32_t>(vertexResult.pushConstants.size());
|
||||
|
||||
ShaderRecompiler::ShaderPixelStageInfo pixel{};
|
||||
pixel.inputAddr = inputAddr;
|
||||
pixel.targetOutputMode.at(0) = Float32AbgrExport;
|
||||
pixel.targetExportMapping.fill(0xe4u);
|
||||
const std::vector<std::uint32_t> pixelUserData(8, 0u);
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> pixelMemory{{{reinterpret_cast<std::uintptr_t>(pixelCode.data()), std::as_bytes(pixelCode)}}};
|
||||
ShaderRecompiler::RecompileRequest fragment{
|
||||
{ShaderStage::Fragment, reinterpret_cast<std::uintptr_t>(pixelCode.data()), pixelCode, 0, {}},
|
||||
{waveSize, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory},
|
||||
target,
|
||||
PixelPushLayout(vertexPush, target)
|
||||
};
|
||||
fragment.useCache = false;
|
||||
const auto pixelResult = ShaderRecompiler::Recompile(fragment);
|
||||
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{
|
||||
{ShaderStage::Vertex, &vertexResult, 0},
|
||||
{ShaderStage::Fragment, &pixelResult, PixelPushOffset(vertexPush, target)}
|
||||
}};
|
||||
|
||||
VkPipelineColorBlendAttachmentState blend{};
|
||||
blend.blendEnable = VK_FALSE;
|
||||
blend.colorWriteMask = 0xf;
|
||||
AgcDriver::Graphics::State state{};
|
||||
state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, waveSize, waveSize, std::nullopt, std::nullopt};
|
||||
state.colors = {color};
|
||||
state.color = color;
|
||||
state.hasColorTarget = true;
|
||||
state.renderExtent = {Width, Height};
|
||||
state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
state.viewport = {0, static_cast<float>(Height), static_cast<float>(Width), -static_cast<float>(Height), 0, 1};
|
||||
state.negativeOneToOne = false;
|
||||
state.scissor = {{0, 0}, {Width, Height}};
|
||||
state.cullMode = VK_CULL_MODE_NONE;
|
||||
state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
||||
state.blends = {blend};
|
||||
state.blend = blend;
|
||||
state.blendConstants = {};
|
||||
const AgcDriver::Pm4::DrawParameters draw{0, static_cast<std::uint32_t>(Triangle.size()), 0, 1, 0, false};
|
||||
device.Draw(state, draw, shaders);
|
||||
}
|
||||
|
||||
|
||||
void Check(const std::vector<std::uint8_t>& stored, const std::string& what) {
|
||||
for (std::uint32_t y = 0; y < Height; ++y) {
|
||||
for (std::uint32_t x = 0; x < Width; ++x) {
|
||||
std::array<float, 4> texel{};
|
||||
std::memcpy(texel.data(), stored.data() + (static_cast<std::size_t>(y) * Width + x) * 16u, sizeof(texel));
|
||||
const std::array<float, 4> expected{static_cast<float>(x), static_cast<float>(y), 0.0f, 1.0f};
|
||||
Require(texel == expected, what + ": pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") holds (" + std::to_string(texel[0]) + ", " + std::to_string(texel[1]) + ", " + std::to_string(texel[2]) + ", " + std::to_string(texel[3]) + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Expect(AgcDriver::VulkanDevice& device) {
|
||||
using ShaderRecompiler::PixelInput;
|
||||
using ShaderRecompiler::PixelInputBit;
|
||||
constexpr std::size_t bytes = std::size_t{Width} * Height * 16u;
|
||||
Block block(bytes);
|
||||
std::fill(block.data, block.data + bytes, std::uint8_t{0xff});
|
||||
const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(device);
|
||||
if (AgcDriver::Graphics::HostImportFor(context, block.Address(), bytes) == nullptr) return false;
|
||||
const auto color = DecodeTarget(block, Rgba32FloatInfo);
|
||||
Require(color.tileMode == AgcDriver::Graphics::ColorTileMode::Linear && color.bytes == bytes, "the target is not a linear R32G32B32A32 target");
|
||||
Draw(device, color, PixelInputBit(PixelInput::PositionFixedPoint), Program(0u, NoInput));
|
||||
const auto* recorder = AgcDriver::Graphics::Recorder::Active();
|
||||
Require(recorder != nullptr && !recorder->Idle(), "the draw into an imported linear target waited for the GPU instead of being recorded");
|
||||
device.WaitIdle();
|
||||
std::vector<std::uint8_t> stored(bytes);
|
||||
std::memcpy(stored.data(), block.data, bytes);
|
||||
Check(stored, "after the recorded draw, raw guest read");
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
if (!Expect(*device)) {
|
||||
std::puts("linear target resident test skipped: no host import on this device");
|
||||
return VulkanTestSkipped;
|
||||
}
|
||||
std::puts("linear target resident tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user