perf(agc): use device-local tile buffers and cache spir-v validation
This commit is contained in:
parent
7ed5a0beea
commit
46c799da67
|
|
@ -33,6 +33,7 @@ private:
|
|||
std::unique_ptr<Buffer> tiled;
|
||||
std::unique_ptr<Buffer> linear;
|
||||
std::unique_ptr<Buffer> readback;
|
||||
std::unique_ptr<Buffer> upload;
|
||||
std::uint32_t width = 0;
|
||||
std::uint32_t height = 0;
|
||||
ColorTileMode mode = ColorTileMode::Linear;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ private:
|
|||
VkDeviceSize allocationBytes = 0;
|
||||
VkBufferUsageFlags usage;
|
||||
VkMemoryPropertyFlags properties;
|
||||
bool reusable = false;
|
||||
std::shared_ptr<BufferPool> cache;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ BufferPool::~BufferPool() {
|
|||
}
|
||||
|
||||
void BufferPool::destroy(const BufferAllocation& allocation) noexcept {
|
||||
unmap(device, allocation.memory);
|
||||
if (allocation.mapping != nullptr) unmap(device, allocation.memory);
|
||||
destroyBuffer(device, allocation.buffer, nullptr);
|
||||
freeMemory(device, allocation.memory, nullptr);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,9 +72,10 @@ void GpuColorTransfer::prepare(std::uint32_t newWidth, std::uint32_t newHeight,
|
|||
Require(layout.Bytes() <= context.limits.maxStorageBufferRange && layout.LinearBytes() <= context.limits.maxStorageBufferRange, "color transfer exceeds storage buffer limits");
|
||||
Require((newWidth + 7u) / 8u <= context.limits.maxComputeWorkGroupCount[0] && (newHeight + 7u) / 8u <= context.limits.maxComputeWorkGroupCount[1], "color transfer exceeds workgroup limits");
|
||||
if (tiled && width == newWidth && height == newHeight && mode == newMode) return;
|
||||
auto newTiled = std::make_unique<Buffer>(context, layout.Bytes(), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
|
||||
auto newLinear = std::make_unique<Buffer>(context, layout.LinearBytes(), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT);
|
||||
auto newTiled = std::make_unique<Buffer>(context, layout.Bytes(), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
auto newLinear = std::make_unique<Buffer>(context, layout.LinearBytes(), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
auto newReadback = std::make_unique<Buffer>(context, layout.Bytes(), VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT);
|
||||
auto newUpload = std::make_unique<Buffer>(context, layout.Bytes(), VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
|
||||
const std::array<VkDescriptorBufferInfo, 2> buffers{{{newTiled->Handle(), 0, layout.Bytes()}, {newLinear->Handle(), 0, layout.LinearBytes()}}};
|
||||
std::array<VkWriteDescriptorSet, 2> writes{};
|
||||
for (std::uint32_t i = 0; i < writes.size(); ++i) {
|
||||
|
|
@ -89,6 +90,7 @@ void GpuColorTransfer::prepare(std::uint32_t newWidth, std::uint32_t newHeight,
|
|||
tiled = std::move(newTiled);
|
||||
linear = std::move(newLinear);
|
||||
readback = std::move(newReadback);
|
||||
upload = std::move(newUpload);
|
||||
width = newWidth;
|
||||
height = newHeight;
|
||||
mode = newMode;
|
||||
|
|
@ -99,7 +101,7 @@ void GpuColorTransfer::Upload(std::uint64_t address, std::uint32_t newWidth, std
|
|||
prepare(newWidth, newHeight, newMode);
|
||||
timing.Mark("prepare");
|
||||
const ColorTargetLayout layout(width, height, mode);
|
||||
GuestMemory::Read(address, tiled->Bytes(), layout.Alignment());
|
||||
GuestMemory::Read(address, upload->Bytes(), layout.Alignment());
|
||||
timing.Mark("guest_read", layout.Bytes());
|
||||
}
|
||||
|
||||
|
|
@ -117,11 +119,18 @@ void GpuColorTransfer::convert(VkCommandBuffer commands, bool toTiled, bool swap
|
|||
context.Function<PFN_vkCmdDispatch>("vkCmdDispatch")(commands, (width + 7u) / 8u, (height + 7u) / 8u, 1);
|
||||
VkMemoryBarrier after{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
after.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
|
||||
after.dstAccessMask = toTiled ? VK_ACCESS_HOST_READ_BIT : VK_ACCESS_TRANSFER_READ_BIT;
|
||||
barrier(commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, toTiled ? VK_PIPELINE_STAGE_HOST_BIT : VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &after, 0, nullptr, 0, nullptr);
|
||||
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);
|
||||
}
|
||||
|
||||
void GpuColorTransfer::Detile(VkCommandBuffer commands, bool swapRedBlue) {
|
||||
Require(commands != VK_NULL_HANDLE && upload && tiled, "color upload is not prepared");
|
||||
VkMemoryBarrier before{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
before.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
before.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
context.Function<PFN_vkCmdPipelineBarrier>("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &before, 0, nullptr, 0, nullptr);
|
||||
const VkBufferCopy copy{0, 0, upload->Bytes().size()};
|
||||
context.Function<PFN_vkCmdCopyBuffer>("vkCmdCopyBuffer")(commands, upload->Handle(), tiled->Handle(), 1, ©);
|
||||
convert(commands, false, swapRedBlue);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ Buffer::Buffer(const Context& context, std::size_t size, VkBufferUsageFlags usag
|
|||
mapping = allocation->mapping;
|
||||
deviceAddress = allocation->address;
|
||||
allocationBytes = allocation->allocationBytes;
|
||||
reusable = true;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
|
@ -35,7 +36,8 @@ Buffer::Buffer(const Context& context, std::size_t size, VkBufferUsageFlags usag
|
|||
Check(context.Function<PFN_vkAllocateMemory>("vkAllocateMemory")(context.device, &allocation, nullptr, &memory), "vkAllocateMemory buffer");
|
||||
Check(context.Function<PFN_vkBindBufferMemory>("vkBindBufferMemory")(context.device, buffer, memory, 0), "vkBindBufferMemory");
|
||||
initializeAddress(usage);
|
||||
Check(context.Function<PFN_vkMapMemory>("vkMapMemory")(context.device, memory, 0, VK_WHOLE_SIZE, 0, &mapping), "vkMapMemory");
|
||||
if ((properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0) Check(context.Function<PFN_vkMapMemory>("vkMapMemory")(context.device, memory, 0, VK_WHOLE_SIZE, 0, &mapping), "vkMapMemory");
|
||||
reusable = true;
|
||||
} catch (...) {
|
||||
release();
|
||||
throw;
|
||||
|
|
@ -47,7 +49,7 @@ Buffer::~Buffer() {
|
|||
}
|
||||
|
||||
void Buffer::release() noexcept {
|
||||
if (mapping && buffer && memory && cache) {
|
||||
if (reusable && buffer && memory && cache) {
|
||||
cache->Put({buffer, memory, mapping, deviceAddress, allocationBytes, size, usage, properties});
|
||||
return;
|
||||
}
|
||||
|
|
@ -61,10 +63,12 @@ VkBuffer Buffer::Handle() const {
|
|||
}
|
||||
|
||||
std::span<std::byte> Buffer::Bytes() {
|
||||
Require(mapping != nullptr, "GPU-only buffer has no CPU mapping");
|
||||
return {static_cast<std::byte*>(mapping), size};
|
||||
}
|
||||
|
||||
void Buffer::Invalidate() {
|
||||
Require(mapping != nullptr, "cannot invalidate an unmapped GPU buffer");
|
||||
VkMappedMemoryRange range{VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE};
|
||||
range.memory = memory;
|
||||
range.size = VK_WHOLE_SIZE;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "BdaAbi.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/VertexInput.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
|
||||
#include <spirv/unified1/spirv.hpp>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
|
@ -8,6 +9,8 @@
|
|||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
namespace AgcDriver::Graphics {
|
||||
namespace {
|
||||
|
|
@ -463,6 +466,76 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
|||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct ValidatedInterface {
|
||||
std::map<std::uint32_t, std::string> inputs;
|
||||
std::map<std::uint32_t, std::string> outputs;
|
||||
};
|
||||
|
||||
std::shared_ptr<const ValidatedInterface> inspectCached(const CompiledShader& compiled, const State& state, const VkPhysicalDeviceSubgroupProperties& subgroup, bool fragmentShaderBarycentric) {
|
||||
PerformanceTimer timing("Graphics.ShaderValidation");
|
||||
Require(compiled.program != nullptr, "missing compiled shader");
|
||||
const auto& shader = *compiled.program;
|
||||
std::string key;
|
||||
const auto append = [&]<typename TValue>(const TValue& value) {
|
||||
static_assert(std::is_trivially_copyable_v<TValue>);
|
||||
key.append(reinterpret_cast<const char*>(&value), sizeof(value));
|
||||
};
|
||||
append(compiled.stage);
|
||||
append(state.rectList);
|
||||
append(state.stages.mesh.has_value());
|
||||
if (state.stages.mesh) {
|
||||
append(state.stages.mesh->threadsPerGroup);
|
||||
append(state.stages.mesh->maxVertices);
|
||||
append(state.stages.mesh->maxPrimitives);
|
||||
}
|
||||
append(state.stages.tessellation.has_value());
|
||||
if (state.stages.tessellation) {
|
||||
append(state.stages.tessellation->inputControlPoints);
|
||||
append(state.stages.tessellation->outputControlPoints);
|
||||
}
|
||||
append(subgroup.supportedStages);
|
||||
append(subgroup.supportedOperations);
|
||||
append(fragmentShaderBarycentric);
|
||||
append(shader.bdaAbiVersion);
|
||||
append(shader.pushConstants.empty());
|
||||
append(shader.bindings.size());
|
||||
for (const auto& binding : shader.bindings) {
|
||||
append(binding.kind);
|
||||
append(binding.role);
|
||||
append(binding.descriptorSet);
|
||||
append(binding.binding);
|
||||
append(binding.count);
|
||||
append(binding.readOnly);
|
||||
append(binding.guestDescriptor.empty());
|
||||
}
|
||||
append(shader.vertexAttributes.size());
|
||||
for (const auto& attribute : shader.vertexAttributes) {
|
||||
append(attribute.location);
|
||||
append(attribute.components);
|
||||
append(attribute.resource.fields[3]);
|
||||
}
|
||||
append(shader.spirv.size());
|
||||
const auto code = std::as_bytes(std::span(shader.spirv));
|
||||
if (!code.empty()) key.append(reinterpret_cast<const char*>(code.data()), code.size());
|
||||
timing.Mark("key", key.size());
|
||||
static thread_local std::map<std::string, std::shared_ptr<const ValidatedInterface>> cache;
|
||||
const auto found = cache.find(key);
|
||||
if (found != cache.end()) {
|
||||
timing.Mark("hit");
|
||||
return found->second;
|
||||
}
|
||||
auto module = Inspect(compiled, state, subgroup, fragmentShaderBarycentric);
|
||||
auto result = std::make_shared<const ValidatedInterface>(ValidatedInterface{std::move(module.inputs), std::move(module.outputs)});
|
||||
if (cache.size() >= 128) cache.clear();
|
||||
cache.emplace(std::move(key), result);
|
||||
timing.Mark("inspect");
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ValidateShaders(std::span<const CompiledShader> shaders, const State& state, const VkPhysicalDeviceSubgroupProperties& subgroup, bool fragmentShaderBarycentric) {
|
||||
using Stage = ShaderRecompiler::ShaderStage;
|
||||
const bool tessellation = state.stages.path == ShaderPath::Tessellation;
|
||||
|
|
@ -473,22 +546,22 @@ void ValidateShaders(std::span<const CompiledShader> shaders, const State& state
|
|||
Require(shaders.size() == (tessellation || state.rectList ? 4u : 2u), "incorrect graphics stage count");
|
||||
const std::array<Stage, 4> tessStages{Stage::Local, Stage::TessellationControl, Stage::TessellationEvaluation, Stage::Fragment};
|
||||
static_cast<void>(AssemblePushConstants(shaders));
|
||||
Module previous;
|
||||
std::shared_ptr<const ValidatedInterface> previous;
|
||||
for (std::size_t i = 0; i < shaders.size(); ++i) {
|
||||
const auto expected = state.rectList ? (i == 0 ? Stage::Vertex : tessStages[i]) : tessellation ? tessStages[i] : i == 1 ? Stage::Fragment : state.stages.path == ShaderPath::Geometry ? Stage::Mesh : Stage::Vertex;
|
||||
Require(shaders[i].program != nullptr, "missing compiled shader");
|
||||
Require(shaders[i].stage == expected, "graphics stage order disagrees");
|
||||
for (const auto& binding : shaders[i].program->bindings) Require(binding.descriptorSet == 0, "graphics resource uses a descriptor set other than zero");
|
||||
const auto current = Inspect(shaders[i], state, subgroup, fragmentShaderBarycentric);
|
||||
const auto current = inspectCached(shaders[i], state, subgroup, fragmentShaderBarycentric);
|
||||
if (i != 0) {
|
||||
for (const auto& [location, signature] : current.inputs) {
|
||||
const auto output = previous.outputs.find(location);
|
||||
Require(output != previous.outputs.end() && output->second == signature, "graphics interfaces disagree at location " + std::to_string(location));
|
||||
for (const auto& [location, signature] : current->inputs) {
|
||||
const auto output = previous->outputs.find(location);
|
||||
Require(output != previous->outputs.end() && output->second == signature, "graphics interfaces disagree at location " + std::to_string(location));
|
||||
}
|
||||
}
|
||||
previous = current;
|
||||
}
|
||||
Require(previous.outputs.size() == 1 && previous.outputs.contains(0) && previous.outputs.at(0) == "vertex:f32x4", "fragment shader must export one float4 color at location zero");
|
||||
Require(previous->outputs.size() == 1 && previous->outputs.contains(0) && previous->outputs.at(0) == "vertex:f32x4", "fragment shader must export one float4 color at location zero");
|
||||
}
|
||||
|
||||
void ValidateShaderPair(const ShaderRecompiler::RecompileResult& vertex, const ShaderRecompiler::RecompileResult& fragment) {
|
||||
|
|
|
|||
|
|
@ -68,10 +68,50 @@ void checkBufferReuse(const Context& context) {
|
|||
Require(simultaneous.Handle() != reused.Handle(), "buffer cache reused an active allocation");
|
||||
}
|
||||
|
||||
void checkDeviceBuffer(const Context& context) {
|
||||
constexpr std::size_t bytes = 4096;
|
||||
constexpr auto usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
VkBuffer handle = VK_NULL_HANDLE;
|
||||
{
|
||||
Buffer local(context, bytes, usage, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
handle = local.Handle();
|
||||
bool rejected = false;
|
||||
try { static_cast<void>(local.Bytes()); }
|
||||
catch (const std::runtime_error&) { rejected = true; }
|
||||
Require(rejected, "GPU-only buffer exposed a CPU mapping");
|
||||
rejected = false;
|
||||
try { local.Invalidate(); }
|
||||
catch (const std::runtime_error&) { rejected = true; }
|
||||
Require(rejected, "GPU-only buffer accepted host invalidation");
|
||||
Buffer upload(context, bytes, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
|
||||
Buffer readback(context, bytes, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
|
||||
std::fill(upload.Bytes().begin(), upload.Bytes().end(), std::byte{0x5a});
|
||||
CommandBatch batch(context);
|
||||
VkMemoryBarrier barrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
|
||||
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
const auto sync = context.Function<PFN_vkCmdPipelineBarrier>("vkCmdPipelineBarrier");
|
||||
sync(batch.Handle(), VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &barrier, 0, nullptr, 0, nullptr);
|
||||
const VkBufferCopy copy{0, 0, bytes};
|
||||
const auto transfer = context.Function<PFN_vkCmdCopyBuffer>("vkCmdCopyBuffer");
|
||||
transfer(batch.Handle(), upload.Handle(), local.Handle(), 1, ©);
|
||||
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
sync(batch.Handle(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &barrier, 0, nullptr, 0, nullptr);
|
||||
transfer(batch.Handle(), local.Handle(), readback.Handle(), 1, ©);
|
||||
barrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
|
||||
sync(batch.Handle(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 1, &barrier, 0, nullptr, 0, nullptr);
|
||||
batch.SubmitAndWait();
|
||||
Require(std::equal(upload.Bytes().begin(), upload.Bytes().end(), readback.Bytes().begin()), "device-local buffer transfer corrupted data");
|
||||
}
|
||||
Buffer reused(context, bytes, usage, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
Require(reused.Handle() == handle, "device-local buffer was not retained for reuse");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void RunColorTransferTests(const AgcDriver::Graphics::Context& context) {
|
||||
checkBufferReuse(context);
|
||||
checkDeviceBuffer(context);
|
||||
checkConversion(context, 130, 129, AgcDriver::Graphics::ColorTileMode::RenderTarget, false);
|
||||
checkConversion(context, 257, 17, AgcDriver::Graphics::ColorTileMode::RenderTarget, true);
|
||||
checkConversion(context, 192, 13, AgcDriver::Graphics::ColorTileMode::Linear, false);
|
||||
|
|
|
|||
Loading…
Reference in New Issue