mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-18 23:07:07 -05:00
Merge pull request #6175 from mitul72/fix/agc-pass-hazard-inputs
fix(agc): name in-place draw inputs and multisampled targets in pass hazards
This commit is contained in:
5 files changed
+401
-4
No files matched your search
@@ -54,8 +54,8 @@ std::size_t MultisampledDccBytes(const ColorTarget& target);
|
||||
VkClearColorValue MultisampledDccClear(const ColorTarget& target, std::span<const std::byte> dcc, std::span<const std::byte> cmask);
|
||||
bool MultisampledFillClearColor(const ColorTarget& target, std::uint32_t fill, VkClearColorValue& clear);
|
||||
bool TakeMultisampledClear(MultisampledCmask& cmask, MultisampledColorFill& colorFill, const ColorTarget& use, VkClearColorValue& clear);
|
||||
VkImageView MultisampleTargetView(const Context& context, const ColorTarget& target);
|
||||
std::shared_ptr<Texture> MultisampleTargetTexture(const Context& context, const GuestTextureResource& resource, VkComponentMapping components, VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D);
|
||||
VkImageView MultisampleTargetView(const Context& context, const ColorTarget& target, VkImage* image = nullptr);
|
||||
void MaterializeMultisampleTargetClear(const Context& context, const ColorTarget& target);
|
||||
VkImageView MultisampleTargetStorageView(const Context& context, const GuestTextureResource& resource);
|
||||
bool MultisampleTargetStorable(const Context& context, const ColorTarget& target);
|
||||
|
||||
@@ -1602,6 +1602,7 @@ struct RecordedDraw {
|
||||
std::vector<std::shared_ptr<StorageTexture>> targets;
|
||||
std::vector<std::uint32_t> targetSlices;
|
||||
std::vector<std::shared_ptr<StorageTexture>> proxies;
|
||||
std::vector<VkImage> multisampleImages;
|
||||
const IndirectRecord* indirect = nullptr;
|
||||
std::span<const ShaderResources::MovedBuffer> moved;
|
||||
bool listed = false;
|
||||
@@ -1798,12 +1799,14 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam
|
||||
passReads.emplace_back(args->arguments, args->arguments + args->RangeBytes());
|
||||
if (args->countIndirect) passReads.emplace_back(args->countAddress, args->countAddress + 4);
|
||||
}
|
||||
passReads.insert(passReads.end(), inputs.inPlaceReads.begin(), inputs.inPlaceReads.end());
|
||||
const auto passImages = resources.StorageImages();
|
||||
std::vector<std::pair<VkImage, bool>> passAttachments;
|
||||
passAttachments.reserve(record.targets.size());
|
||||
for (const auto& target : record.targets) {
|
||||
if (target != nullptr) passAttachments.emplace_back(target->Image(), true);
|
||||
}
|
||||
for (const auto image : record.multisampleImages) passAttachments.emplace_back(image, true);
|
||||
const bool unknownMemory = addressBased || resources.UsesAddressTable() || resources.HasCopiedWrites();
|
||||
static const bool addressReadPasses = std::getenv("APS5_NO_ADDRESS_READ_PASSES") == nullptr;
|
||||
const bool readOnlyBda = addressReadPasses && resources.BdaReadOnly();
|
||||
@@ -2026,6 +2029,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
|
||||
TileMipLayout mip{};
|
||||
std::unique_ptr<RenderTarget> target;
|
||||
bool multisampled = false;
|
||||
VkImage multisampleImage = VK_NULL_HANDLE;
|
||||
// APS5_DUMP_TARGETS: the rendered linear pixels, read back for target_<address>_<n>.raw.
|
||||
std::unique_ptr<Buffer> dump;
|
||||
};
|
||||
@@ -2042,7 +2046,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
|
||||
if (color.samples > 1) {
|
||||
binding.multisampled = true;
|
||||
timer.phase(PhaseSetup);
|
||||
targetViews.push_back(MultisampleTargetView(context, color));
|
||||
targetViews.push_back(MultisampleTargetView(context, color, &binding.multisampleImage));
|
||||
timer.phase(PhaseReadTarget);
|
||||
continue;
|
||||
}
|
||||
@@ -2323,6 +2327,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
|
||||
for (const auto& binding : targets) record.targetSlices.push_back(binding.color.arraySlice);
|
||||
for (const auto& binding : targets) {
|
||||
if (binding.proxied) record.proxies.push_back(binding.resident);
|
||||
if (binding.multisampled) record.multisampleImages.push_back(binding.multisampleImage);
|
||||
}
|
||||
record.indirect = args != nullptr ? &indirect : nullptr;
|
||||
record.listed = listed;
|
||||
|
||||
@@ -709,9 +709,11 @@ bool TakeMultisampledClear(MultisampledCmask& cmask, MultisampledColorFill& colo
|
||||
return true;
|
||||
}
|
||||
|
||||
VkImageView MultisampleTargetView(const Context& context, const ColorTarget& target) {
|
||||
VkImageView MultisampleTargetView(const Context& context, const ColorTarget& target, VkImage* image) {
|
||||
std::lock_guard lock(targetsMutex());
|
||||
return acquire(context, target).view;
|
||||
const auto& acquired = acquire(context, target);
|
||||
if (image != nullptr) *image = acquired.image;
|
||||
return acquired.view;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
|
||||
#include "DepthFastClearHarness.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace AgcDriver {
|
||||
|
||||
struct VulkanDeviceTestAccess {
|
||||
static Graphics::Context ContextOf(const VulkanDevice& device) { return device.graphicsContext(); }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace AgcDriver::Graphics;
|
||||
using DepthFastClearHarness::Compile;
|
||||
using DepthFastClearHarness::Height;
|
||||
using DepthFastClearHarness::Shaders;
|
||||
using DepthFastClearHarness::Triangles;
|
||||
using DepthFastClearHarness::Width;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Samples = 4;
|
||||
|
||||
alignas(4096) std::array<std::byte, Width * Height * 4 * Samples> Multisampled{};
|
||||
alignas(4096) std::array<float, Width * Height> DepthA{};
|
||||
alignas(4096) std::array<float, Width * Height> DepthB{};
|
||||
|
||||
struct Barrier {
|
||||
VkPipelineStageFlags source;
|
||||
VkPipelineStageFlags destination;
|
||||
std::vector<VkMemoryBarrier> memory;
|
||||
std::vector<VkImageMemoryBarrier> images;
|
||||
};
|
||||
|
||||
class Capture {
|
||||
public:
|
||||
static Context Install(const Context& base) {
|
||||
Require(resolver == nullptr, "nested barrier capture");
|
||||
resolver = base.deviceProc;
|
||||
device = base.device;
|
||||
Context captured = base;
|
||||
captured.bufferPool.reset();
|
||||
captured.deviceProc = resolve;
|
||||
captured.functions = nullptr;
|
||||
return captured;
|
||||
}
|
||||
static std::vector<Barrier> Take() { return std::exchange(barriers, {}); }
|
||||
|
||||
private:
|
||||
static PFN_vkVoidFunction VKAPI_CALL resolve(VkDevice handle, const char* name) {
|
||||
if (std::string_view(name) == "vkCmdPipelineBarrier") return reinterpret_cast<PFN_vkVoidFunction>(pipelineBarrier);
|
||||
return resolver(handle, name);
|
||||
}
|
||||
static VKAPI_ATTR void VKAPI_CALL pipelineBarrier(VkCommandBuffer commands, VkPipelineStageFlags source, VkPipelineStageFlags destination, VkDependencyFlags dependencies, std::uint32_t memoryCount, const VkMemoryBarrier* memory, std::uint32_t bufferCount, const VkBufferMemoryBarrier* buffers, std::uint32_t imageCount, const VkImageMemoryBarrier* images) {
|
||||
barriers.push_back({source, destination, std::vector<VkMemoryBarrier>(memory, memory + memoryCount), std::vector<VkImageMemoryBarrier>(images, images + imageCount)});
|
||||
reinterpret_cast<PFN_vkCmdPipelineBarrier>(resolver(device, "vkCmdPipelineBarrier"))(commands, source, destination, dependencies, memoryCount, memory, bufferCount, buffers, imageCount, images);
|
||||
}
|
||||
|
||||
static inline PFN_vkGetDeviceProcAddr resolver = nullptr;
|
||||
static inline VkDevice device = VK_NULL_HANDLE;
|
||||
static inline std::vector<Barrier> barriers;
|
||||
};
|
||||
|
||||
bool OrdersColorWrites(const std::vector<Barrier>& barriers) {
|
||||
return std::any_of(barriers.begin(), barriers.end(), [](const Barrier& barrier) {
|
||||
return barrier.source == VK_PIPELINE_STAGE_ALL_COMMANDS_BIT && barrier.destination == VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT && barrier.images.empty() && barrier.memory.size() == 1 && (barrier.memory[0].srcAccessMask & VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT) != 0 && (barrier.memory[0].dstAccessMask & VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT) != 0;
|
||||
});
|
||||
}
|
||||
|
||||
void Draw(const Context& context, AgcDriver::VulkanDevice& device, const Shaders& compiled, std::uint64_t depth) {
|
||||
const std::array<CompiledShader, 2> shaders{{
|
||||
{ShaderStage::Vertex, &compiled.vertex, 0},
|
||||
{ShaderStage::Fragment, &compiled.pixel, PixelPushOffset(compiled.vertexPush, device.Target())}
|
||||
}};
|
||||
State state{};
|
||||
state.stages = {ShaderPath::Vertex, 0u, 32u, 32u, std::nullopt, std::nullopt};
|
||||
state.color = {reinterpret_cast<std::uintptr_t>(Multisampled.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Multisampled.size(), 0xe4u};
|
||||
state.color.samples = Samples;
|
||||
state.colors = {state.color};
|
||||
state.hasColorTarget = true;
|
||||
state.depth = DepthTarget{depth, 0, {Width, Height}, VK_FORMAT_D32_SFLOAT, 1.0f, 0, 0, false, Samples};
|
||||
state.depthTest = true;
|
||||
state.depthWrite = true;
|
||||
state.depthCompare = VK_COMPARE_OP_LESS;
|
||||
state.samples = Samples;
|
||||
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.blend.colorWriteMask = 15;
|
||||
state.blends = {state.blend};
|
||||
state.blendConstants = {};
|
||||
const AgcDriver::Pm4::DrawParameters draw{0, static_cast<std::uint32_t>(Triangles().size()), 0, 1, 0, false};
|
||||
AgcDriver::Graphics::Draw(context, state, draw, shaders);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
const auto base = AgcDriver::VulkanDeviceTestAccess::ContextOf(*device);
|
||||
if ((base.limits.framebufferColorSampleCounts & Samples) == 0 || (base.limits.framebufferDepthSampleCounts & Samples) == 0) {
|
||||
std::puts("skipped, the device cannot render four-sample color and depth");
|
||||
return VulkanTestSkipped;
|
||||
}
|
||||
if (!Recorder::MergeBarriers()) {
|
||||
std::puts("skipped, barrier elision is off");
|
||||
return VulkanTestSkipped;
|
||||
}
|
||||
const auto shaders = Compile(*device);
|
||||
device->WaitIdle();
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
const auto captured = Capture::Install(base);
|
||||
Recorder recorder(captured);
|
||||
recorder.Activate();
|
||||
const auto depthA = reinterpret_cast<std::uintptr_t>(DepthA.data());
|
||||
const auto depthB = reinterpret_cast<std::uintptr_t>(DepthB.data());
|
||||
Draw(captured, *device, shaders, depthA);
|
||||
Draw(captured, *device, shaders, depthB);
|
||||
Capture::Take();
|
||||
Draw(captured, *device, shaders, depthA);
|
||||
const auto between = Capture::Take();
|
||||
Require(OrdersColorWrites(between), "multisampled pass order: two draws sharing a multisampled color target while switching depth targets recorded no barrier ordering the first draw's color writes before the second");
|
||||
recorder.Submit();
|
||||
recorder.Sync();
|
||||
std::puts("multisampled pass order tests passed (ordering asserted on the recorded barriers; the multisampled image is not resolved to guest memory)");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Recorder.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 "DepthFastClearHarness.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 <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::ColorTargetLayout;
|
||||
using AgcDriver::Graphics::ColorTileMode;
|
||||
using DepthFastClearHarness::BufferDescriptor;
|
||||
using DepthFastClearHarness::Height;
|
||||
using DepthFastClearHarness::Require;
|
||||
using DepthFastClearHarness::Triangles;
|
||||
using DepthFastClearHarness::VertexCode;
|
||||
using DepthFastClearHarness::Width;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::size_t BlockBytes = 65536;
|
||||
const ColorTargetLayout TargetLayout(Width, Height, ColorTileMode::RenderTarget, 4);
|
||||
|
||||
alignas(4096) std::array<float, Width * Height * 2> FirstDepth{};
|
||||
alignas(4096) std::array<float, Width * Height * 2> SecondDepth{};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 4> CoverCode{0x7e0002f2, 0xf800180f, 0x00000000, 0xbf810000};
|
||||
alignas(256) constexpr std::array<std::uint32_t, 8> StoreCode{0x7e0a0280, 0x7e0c0280, 0xe0702000, 0x80010506, 0x7e0e02f2, 0xf800180f, 0x07070707, 0xbf810000};
|
||||
|
||||
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, "pass input hazard: cannot map a block");
|
||||
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, "pass input hazard: cannot allocate a block");
|
||||
std::memset(data, 0, bytes);
|
||||
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;
|
||||
};
|
||||
|
||||
struct Program {
|
||||
ShaderRecompiler::RecompileResult vertex;
|
||||
ShaderRecompiler::RecompileResult pixel;
|
||||
std::uint32_t vertexPush = 0;
|
||||
};
|
||||
|
||||
Program Compile(AgcDriver::VulkanDevice& device, const void* vertices, std::span<const std::uint32_t> pixelCode, const void* stored) {
|
||||
const auto target = device.Target();
|
||||
Program program;
|
||||
std::vector<std::uint32_t> vertexUserData(4, 0u);
|
||||
const auto vertexBuffer = BufferDescriptor(vertices, 16u, static_cast<std::uint32_t>(Triangles().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, {}},
|
||||
{32, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory},
|
||||
target,
|
||||
{0, 0, 0, 64}
|
||||
};
|
||||
vertex.useCache = false;
|
||||
program.vertex = ShaderRecompiler::Recompile(vertex);
|
||||
program.vertexPush = static_cast<std::uint32_t>(program.vertex.pushConstants.size());
|
||||
|
||||
ShaderRecompiler::ShaderPixelStageInfo pixel{};
|
||||
pixel.wave32 = true;
|
||||
pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionX) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionY);
|
||||
pixel.posX = true;
|
||||
pixel.posY = true;
|
||||
pixel.targetOutputMode[0] = 9;
|
||||
pixel.targetExportMapping.fill(0xe4u);
|
||||
std::vector<std::uint32_t> pixelUserData(8, 0u);
|
||||
if (stored != nullptr) {
|
||||
const auto storeBuffer = BufferDescriptor(stored, 4u, 1u);
|
||||
std::copy(storeBuffer.begin(), storeBuffer.end(), pixelUserData.begin() + 4);
|
||||
}
|
||||
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, {}},
|
||||
{32, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory},
|
||||
target,
|
||||
PixelPushLayout(program.vertexPush, target)
|
||||
};
|
||||
fragment.useCache = false;
|
||||
program.pixel = ShaderRecompiler::Recompile(fragment);
|
||||
return program;
|
||||
}
|
||||
|
||||
void Draw(AgcDriver::VulkanDevice& device, const Program& program, const Block& target, const float* depth, const std::uint32_t* indices) {
|
||||
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{
|
||||
{ShaderStage::Vertex, &program.vertex, 0},
|
||||
{ShaderStage::Fragment, &program.pixel, PixelPushOffset(program.vertexPush, device.Target())}
|
||||
}};
|
||||
AgcDriver::Graphics::State state{};
|
||||
state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 32u, 32u, std::nullopt, std::nullopt};
|
||||
state.color = {target.Address(), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, TargetLayout.Bytes(), 0xe4u, ColorTileMode::RenderTarget};
|
||||
state.colors = {state.color};
|
||||
state.hasColorTarget = true;
|
||||
if (depth != nullptr) {
|
||||
state.depth = AgcDriver::Graphics::DepthTarget{reinterpret_cast<std::uintptr_t>(depth), 0, {Width, Height}, VK_FORMAT_D32_SFLOAT, 1.0f, 0};
|
||||
state.depthTest = true;
|
||||
state.depthWrite = true;
|
||||
state.depthCompare = VK_COMPARE_OP_ALWAYS;
|
||||
}
|
||||
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.blend.colorWriteMask = 15;
|
||||
state.blends = {state.blend};
|
||||
state.blendConstants = {};
|
||||
const AgcDriver::Pm4::DrawParameters draw{reinterpret_cast<std::uintptr_t>(indices), static_cast<std::uint32_t>(Triangles().size()), indices != nullptr ? 4u : 0u, 1, 0, indices != nullptr};
|
||||
device.Draw(state, draw, shaders);
|
||||
}
|
||||
|
||||
bool WriterFenced(std::uint64_t address, std::size_t bytes) {
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
auto* recorder = AgcDriver::Graphics::Recorder::Active();
|
||||
Require(recorder != nullptr, "pass input hazard: no active recorder");
|
||||
const std::array<std::pair<std::uint64_t, std::uint64_t>, 1> writes{{{address, address + bytes}}};
|
||||
return recorder->StartDrawPass(0x5eed, false, false, AgcDriver::Graphics::PassAccess{{}, writes, {}, {}, false, false}).barrier;
|
||||
}
|
||||
|
||||
std::size_t Covered(AgcDriver::VulkanDevice& device, const Block& target) {
|
||||
AgcDriver::Graphics::StorageTexture::FlushPending(target.Address(), target.bytes, nullptr, "test read-back");
|
||||
device.WaitIdle();
|
||||
std::vector<std::uint8_t> stored(TargetLayout.Bytes());
|
||||
AgcDriver::GuestMemory::Read(target.Address(), std::as_writable_bytes(std::span(stored)), 1);
|
||||
return static_cast<std::size_t>(std::count(stored.begin(), stored.end(), std::uint8_t{0xff}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
const Block inputs(BlockBytes);
|
||||
auto* indices = reinterpret_cast<std::uint32_t*>(inputs.data);
|
||||
constexpr std::array<std::uint32_t, 3> triangle{0u, 1u, 2u};
|
||||
const auto reader = Compile(*device, Triangles().data(), CoverCode, nullptr);
|
||||
const auto writer = Compile(*device, Triangles().data(), StoreCode, indices + 1);
|
||||
constexpr std::size_t all = std::size_t{Width} * Height * 4u;
|
||||
const std::array<std::pair<const float*, const float*>, 3> depths{{{nullptr, nullptr}, {FirstDepth.data(), nullptr}, {FirstDepth.data(), SecondDepth.data()}}};
|
||||
for (const auto& [readDepth, writeDepth] : depths) {
|
||||
const Block read(TargetLayout.Bytes());
|
||||
const Block written(TargetLayout.Bytes());
|
||||
std::memcpy(indices, triangle.data(), sizeof(triangle));
|
||||
const auto variant = std::string(readDepth == nullptr ? "without depth" : writeDepth == nullptr ? "with depth on the reader" : "with a depth target each");
|
||||
Draw(*device, reader, read, readDepth, indices);
|
||||
Require(WriterFenced(inputs.Address(), sizeof(triangle)), "pass input hazard " + variant + ": a pass writing the indices an open pass reads in place took no barrier");
|
||||
Draw(*device, reader, read, readDepth, indices);
|
||||
Draw(*device, writer, written, writeDepth, nullptr);
|
||||
Require(Covered(*device, read) == all, "pass input hazard " + variant + ": the draw reading its indices in place did not cover its target, a later pass overwrote them first");
|
||||
Require(Covered(*device, written) == all, "pass input hazard " + variant + ": the storing draw did not cover its target");
|
||||
std::array<std::uint32_t, 3> stored{};
|
||||
AgcDriver::GuestMemory::Read(inputs.Address(), std::as_writable_bytes(std::span(stored)), 4);
|
||||
Require(stored == std::array<std::uint32_t, 3>{0u, 0u, 2u}, "pass input hazard " + variant + ": the storing draw's write did not reach the indices");
|
||||
}
|
||||
std::puts("pass input hazard tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user