Merge pull request #2169 from authurtabo-byte/feat/agc-uniform-sample-locations
feat(agc): support uniform custom MSAA sample locations
This commit is contained in:
commit
2ccbd376f8
|
|
@ -285,6 +285,7 @@ struct VulkanDevice::State {
|
|||
bool maintenance8 = false;
|
||||
std::uint32_t srgbDecodeFormats = 0;
|
||||
bool storageImageWithoutFormat = false;
|
||||
VkSampleCountFlags sampleLocationSampleCounts = 0;
|
||||
bool depthClamp = false;
|
||||
bool depthBounds = false;
|
||||
bool depthBiasClamp = false;
|
||||
|
|
@ -1151,6 +1152,21 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
|
|||
state->spirvExtensions.push_back("SPV_KHR_physical_storage_buffer");
|
||||
state->depthRangeUnrestricted = hasExtension(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME);
|
||||
if (state->depthRangeUnrestricted) deviceExtensions.push_back(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME);
|
||||
if (hasExtension(VK_EXT_SAMPLE_LOCATIONS_EXTENSION_NAME)) {
|
||||
VkPhysicalDeviceSampleLocationsPropertiesEXT locations{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLE_LOCATIONS_PROPERTIES_EXT};
|
||||
VkPhysicalDeviceProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &locations};
|
||||
state->InstanceFunction<PFN_vkGetPhysicalDeviceProperties2>("vkGetPhysicalDeviceProperties2")(selected, &properties);
|
||||
if (locations.sampleLocationSubPixelBits >= 4 && locations.sampleLocationCoordinateRange[0] <= 0.0f && locations.sampleLocationCoordinateRange[1] >= 15.0f / 16.0f) {
|
||||
const auto multisampleProperties = state->InstanceFunction<PFN_vkGetPhysicalDeviceMultisamplePropertiesEXT>("vkGetPhysicalDeviceMultisamplePropertiesEXT");
|
||||
for (const auto samples : {VK_SAMPLE_COUNT_2_BIT, VK_SAMPLE_COUNT_4_BIT, VK_SAMPLE_COUNT_8_BIT}) {
|
||||
if ((locations.sampleLocationSampleCounts & samples) == 0) continue;
|
||||
VkMultisamplePropertiesEXT multisample{VK_STRUCTURE_TYPE_MULTISAMPLE_PROPERTIES_EXT};
|
||||
multisampleProperties(selected, samples, &multisample);
|
||||
if (multisample.maxSampleLocationGridSize.width != 0 && multisample.maxSampleLocationGridSize.height != 0) state->sampleLocationSampleCounts |= samples;
|
||||
}
|
||||
}
|
||||
if (state->sampleLocationSampleCounts != 0) deviceExtensions.push_back(VK_EXT_SAMPLE_LOCATIONS_EXTENSION_NAME);
|
||||
}
|
||||
// Guest dispatches cover whole thread groups past the edge of small images; robust image access
|
||||
// drops those writes instead of faulting the device.
|
||||
const bool imageRobustness = hasExtension(VK_EXT_IMAGE_ROBUSTNESS_EXTENSION_NAME);
|
||||
|
|
@ -3023,6 +3039,7 @@ Graphics::Context VulkanDevice::buildContext() const {
|
|||
context.imageViewMinLod = state->imageViewMinLod;
|
||||
context.pipelineExecutableInfo = state->pipelineExecutableInfo;
|
||||
context.srgbDecodeFormats = state->srgbDecodeFormats;
|
||||
context.sampleLocationSampleCounts = state->sampleLocationSampleCounts;
|
||||
context.memoryProperties2 = state->memoryBudget ? state->InstanceFunction<PFN_vkGetPhysicalDeviceMemoryProperties2>("vkGetPhysicalDeviceMemoryProperties2") : nullptr;
|
||||
context.singlePassStorage = state->storageImageWithoutFormat;
|
||||
return context;
|
||||
|
|
|
|||
|
|
@ -171,6 +171,7 @@ struct Context {
|
|||
bool provokingVertexLast = false;
|
||||
bool provokingVertexModePerPipeline = false;
|
||||
bool singlePassStorage = false;
|
||||
VkSampleCountFlags sampleLocationSampleCounts = 0;
|
||||
|
||||
template<typename TFunction>
|
||||
TFunction Function(const char* name) const {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ namespace AgcDriver::Graphics {
|
|||
class Texture;
|
||||
|
||||
VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target);
|
||||
bool SampleLocationsCompatibleDepth(const Context& context, VkFormat format, std::uint32_t samples);
|
||||
std::uint64_t DepthSliceBytes(VkExtent2D extent, std::uint32_t bytesPerTexel);
|
||||
void ClearDepthSurfaces(VkDevice device);
|
||||
bool DepthSurfaceAt(std::uint64_t address);
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ private:
|
|||
VkFormat depthFormat = VK_FORMAT_UNDEFINED;
|
||||
VkImageLayout attachmentLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
std::vector<std::uint32_t> colorSlots;
|
||||
bool sampleLocationsEnabled = false;
|
||||
std::vector<CachedFramebuffer> framebuffers;
|
||||
};
|
||||
|
||||
|
|
@ -106,6 +107,7 @@ bool HasStencil(VkFormat format);
|
|||
void ValidateViewport(const Context& context, const VkViewport& viewport);
|
||||
void ValidateDepthBounds(const Context& context, const State& state);
|
||||
void ValidateProvokingVertex(const Context& context, const State& state, std::span<const CompiledShader> shaders = {});
|
||||
bool ValidateSampleLocations(const Context& context, const State& state);
|
||||
|
||||
void ValidateShaderPair(const ShaderRecompiler::RecompileResult& vertex, const ShaderRecompiler::RecompileResult& fragment);
|
||||
bool ShadersWriteMemory(std::span<const CompiledShader> shaders);
|
||||
|
|
|
|||
|
|
@ -116,10 +116,13 @@ struct State {
|
|||
VkPipelineColorBlendAttachmentState blend;
|
||||
std::array<float, 4> blendConstants;
|
||||
std::uint32_t samples = 1;
|
||||
std::array<std::uint8_t, 8> sampleLocations{};
|
||||
bool customSampleLocations = false;
|
||||
};
|
||||
|
||||
ShaderStages DecodeShaderStages(const QueueState& queue, bool vertexWorkgroup = false);
|
||||
State DecodeState(const QueueState& queue, bool vertexWorkgroup = false);
|
||||
std::array<VkSampleLocationEXT, 8> SampleLocations(const State& state);
|
||||
std::array<std::uint8_t, 8> ExportMappings(const State& state);
|
||||
std::array<ShaderRecompiler::ColorExportPacking, 8> ExportPackings(const State& state);
|
||||
ColorTarget DecodeColorBuffer(const Registers& context, std::uint32_t slot);
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ public:
|
|||
const VkImageAspectFlags aspects = VK_IMAGE_ASPECT_DEPTH_BIT | (target.stencilAddress != 0 ? VK_IMAGE_ASPECT_STENCIL_BIT : 0u);
|
||||
try {
|
||||
VkImageCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
||||
if (SampleLocationsCompatibleDepth(context, target.format, target.samples)) info.flags = VK_IMAGE_CREATE_SAMPLE_LOCATIONS_COMPATIBLE_DEPTH_BIT_EXT;
|
||||
info.imageType = VK_IMAGE_TYPE_2D;
|
||||
info.format = target.format;
|
||||
info.extent = {target.extent.width, target.extent.height, 1};
|
||||
|
|
@ -187,6 +188,16 @@ std::uint64_t DepthSliceBytes(VkExtent2D extent, std::uint32_t bytesPerTexel) {
|
|||
return width * height * bytesPerTexel;
|
||||
}
|
||||
|
||||
bool SampleLocationsCompatibleDepth(const Context& context, VkFormat format, std::uint32_t samples) {
|
||||
if (samples <= 1 || (context.sampleLocationSampleCounts & samples) == 0) return false;
|
||||
VkImageFormatProperties properties{};
|
||||
const auto result = context.imageFormatProperties(context.physical, format, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL,
|
||||
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_CREATE_SAMPLE_LOCATIONS_COMPATIBLE_DEPTH_BIT_EXT, &properties);
|
||||
if (result == VK_ERROR_FORMAT_NOT_SUPPORTED) return false;
|
||||
Check(result, "vkGetPhysicalDeviceImageFormatProperties sample locations depth");
|
||||
return (properties.sampleCounts & samples) != 0;
|
||||
}
|
||||
|
||||
VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target) {
|
||||
std::lock_guard lock(surfacesMutex());
|
||||
const bool cleared = target.htileAddress != 0 && clearedHtiles().erase(target.htileAddress) != 0;
|
||||
|
|
|
|||
|
|
@ -50,6 +50,11 @@ std::uint64_t DrawRenderPassKey(const Context& context, const State& state, std:
|
|||
}
|
||||
mix(state.renderExtent.width);
|
||||
mix(state.renderExtent.height);
|
||||
mix(state.samples);
|
||||
mix(state.customSampleLocations);
|
||||
if (state.customSampleLocations) {
|
||||
for (const auto location : state.sampleLocations) mix(location);
|
||||
}
|
||||
if (!context.provokingVertexModePerPipeline) mix(state.provokingVertexMode);
|
||||
return key;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/PipelineLibrary.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/PipelineSpecialization.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/VertexInput.hpp"
|
||||
#include <algorithm>
|
||||
|
|
@ -106,6 +107,14 @@ void ValidateProvokingVertex(const Context& context, const State& state, std::sp
|
|||
}
|
||||
}
|
||||
|
||||
bool ValidateSampleLocations(const Context& context, const State& state) {
|
||||
const bool extensionSupportsSamples = (context.sampleLocationSampleCounts & state.samples) != 0;
|
||||
Require(!state.customSampleLocations || extensionSupportsSamples, "custom sample locations require VK_EXT_sample_locations support for the draw's sample count");
|
||||
const bool enabled = extensionSupportsSamples && (!state.depth || SampleLocationsCompatibleDepth(context, state.depth->format, state.depth->samples));
|
||||
Require(!state.customSampleLocations || enabled, "custom sample locations require a compatible depth format and sample count");
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void ValidateViewport(const Context& context, const VkViewport& viewport) {
|
||||
Require(std::isfinite(viewport.minDepth) && std::isfinite(viewport.maxDepth), "non-finite viewport depth range");
|
||||
Require(context.depthRangeUnrestricted || (viewport.minDepth >= 0 && viewport.minDepth <= 1 && viewport.maxDepth >= 0 && viewport.maxDepth <= 1), "viewport depth outside [0, 1] requires VK_EXT_depth_range_unrestricted");
|
||||
|
|
@ -188,6 +197,17 @@ bool libraryKeys(PipelineLibraryKeys& keys, const Context& context, const State&
|
|||
if (state.stages.tessellation) appendKey(keys.preRasterization, state.stages.tessellation->inputControlPoints);
|
||||
appendKey(keys.fragmentShader, state.samples);
|
||||
appendKey(keys.fragmentOutput, state.samples);
|
||||
const auto sampleLocationsEnabled = ValidateSampleLocations(context, state);
|
||||
appendKey(keys.fragmentShader, sampleLocationsEnabled);
|
||||
appendKey(keys.fragmentOutput, sampleLocationsEnabled);
|
||||
appendKey(keys.fragmentShader, state.customSampleLocations);
|
||||
appendKey(keys.fragmentOutput, state.customSampleLocations);
|
||||
if (state.customSampleLocations) {
|
||||
for (std::uint32_t sample = 0; sample < state.samples; ++sample) {
|
||||
appendKey(keys.fragmentShader, state.sampleLocations[sample]);
|
||||
appendKey(keys.fragmentOutput, state.sampleLocations[sample]);
|
||||
}
|
||||
}
|
||||
for (const auto& blend : state.blends) appendKey(keys.fragmentOutput, blend);
|
||||
for (const auto value : state.blendConstants) appendKey(keys.fragmentOutput, value);
|
||||
appendKey(keys.renderPass, state.blends.size());
|
||||
|
|
@ -206,6 +226,7 @@ bool libraryKeys(PipelineLibraryKeys& keys, const Context& context, const State&
|
|||
|
||||
Pipeline::Pipeline(const Context& context, const State& state, const VertexInputLayout& vertexInput, const ShaderResources& resources, std::span<const CompiledShader> shaders, VkImageLayout attachmentLayout) : context(context), _modules(shaders.size()), attachments(state.colors.size() + (state.depth ? 1u : 0u)), colorAttachments(state.colors.size()), depthBounds(state.depth.has_value() && state.depthBoundsTest), depthBias(state.depth.has_value() && state.depthBias), dynamicRendering(context.graphicsPipelineLibrary) {
|
||||
PerformanceTimer timing("Vulkan.GraphicsPipeline");
|
||||
sampleLocationsEnabled = ValidateSampleLocations(context, state);
|
||||
// A cached pipeline may outlive its device's teardown (see ClearCachedPipelines); it must not keep
|
||||
// the buffer pool, which is reset with the device, alive past it.
|
||||
this->context.bufferPool.reset();
|
||||
|
|
@ -367,6 +388,11 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput
|
|||
}
|
||||
VkPipelineMultisampleStateCreateInfo samples{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
|
||||
samples.rasterizationSamples = static_cast<VkSampleCountFlagBits>(state.samples);
|
||||
const auto locations = SampleLocations(state);
|
||||
VkPipelineSampleLocationsStateCreateInfoEXT sampleLocations{VK_STRUCTURE_TYPE_PIPELINE_SAMPLE_LOCATIONS_STATE_CREATE_INFO_EXT};
|
||||
sampleLocations.sampleLocationsEnable = VK_TRUE;
|
||||
sampleLocations.sampleLocationsInfo = {VK_STRUCTURE_TYPE_SAMPLE_LOCATIONS_INFO_EXT, nullptr, samples.rasterizationSamples, {1, 1}, state.samples, locations.data()};
|
||||
if (sampleLocationsEnabled) samples.pNext = &sampleLocations;
|
||||
VkPipelineDepthStencilStateCreateInfo depthStencil{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
|
||||
depthStencil.depthTestEnable = state.depthTest;
|
||||
depthStencil.depthWriteEnable = state.depthWrite;
|
||||
|
|
@ -563,6 +589,16 @@ void Pipeline::Begin(VkCommandBuffer commands, const Framebuffer& framebuffer, V
|
|||
begin.renderPass = renderPass;
|
||||
begin.framebuffer = framebuffer.Handle();
|
||||
begin.renderArea = {{0, 0}, extent};
|
||||
const auto locations = SampleLocations(state);
|
||||
const VkSampleLocationsInfoEXT info{VK_STRUCTURE_TYPE_SAMPLE_LOCATIONS_INFO_EXT, nullptr, static_cast<VkSampleCountFlagBits>(state.samples), {1, 1}, state.samples, locations.data()};
|
||||
const VkAttachmentSampleLocationsEXT initial{static_cast<std::uint32_t>(state.colors.size()), info};
|
||||
const VkSubpassSampleLocationsEXT post{0, info};
|
||||
VkRenderPassSampleLocationsBeginInfoEXT sampleLocations{VK_STRUCTURE_TYPE_RENDER_PASS_SAMPLE_LOCATIONS_BEGIN_INFO_EXT};
|
||||
sampleLocations.attachmentInitialSampleLocationsCount = state.depth ? 1 : 0;
|
||||
sampleLocations.pAttachmentInitialSampleLocations = state.depth ? &initial : nullptr;
|
||||
sampleLocations.postSubpassSampleLocationsCount = 1;
|
||||
sampleLocations.pPostSubpassSampleLocations = &post;
|
||||
if (sampleLocationsEnabled) begin.pNext = &sampleLocations;
|
||||
context.Resolved(&DeviceFunctions::cmdBeginRenderPass, "vkCmdBeginRenderPass")(commands, &begin, VK_SUBPASS_CONTENTS_INLINE);
|
||||
Continue(commands, state);
|
||||
}
|
||||
|
|
@ -687,6 +723,8 @@ void pipelineKey(std::vector<std::byte>& key, const Context& context, const Stat
|
|||
for (const auto& blend : state.blends) append(key, blend);
|
||||
for (const auto value : state.blendConstants) append(key, value);
|
||||
append(key, state.samples);
|
||||
append(key, state.customSampleLocations);
|
||||
if (state.customSampleLocations) append(key, state.sampleLocations);
|
||||
append(key, state.colors.size());
|
||||
for (const auto& color : state.colors) append(key, color.format);
|
||||
if (state.blends.size() != state.colors.size()) {
|
||||
|
|
|
|||
|
|
@ -327,7 +327,12 @@ std::uint32_t decodeSamples(const Registers& cx) {
|
|||
}
|
||||
Require(log2 <= 3u, "more than eight samples are unsupported");
|
||||
Require(((config >> 20u) & 7u) == log2, "exposing fewer samples than are rasterized is unsupported");
|
||||
const auto samples = 1u << log2;
|
||||
return 1u << log2;
|
||||
}
|
||||
|
||||
void decodeSampleLocations(const Registers& cx, State& state) {
|
||||
const auto samples = state.samples;
|
||||
if (samples == 1) return;
|
||||
const std::span<const std::array<std::int8_t, 2>> standard = samples == 2 ? std::span<const std::array<std::int8_t, 2>>(StandardLocations2) : samples == 4 ? std::span<const std::array<std::int8_t, 2>>(StandardLocations4) : std::span<const std::array<std::int8_t, 2>>(StandardLocations8);
|
||||
for (std::uint32_t pixel = 0; pixel < 4; ++pixel) {
|
||||
for (std::uint32_t sample = 0; sample < samples; ++sample) {
|
||||
|
|
@ -335,10 +340,14 @@ std::uint32_t decodeSamples(const Registers& cx) {
|
|||
const auto bits = (word >> (8u * (sample % 4u))) & 0xffu;
|
||||
const auto x = static_cast<std::int8_t>(static_cast<std::uint8_t>(bits << 4u)) >> 4;
|
||||
const auto y = static_cast<std::int8_t>(static_cast<std::uint8_t>(bits & 0xf0u)) >> 4;
|
||||
Require(x == standard[sample][0] && y == standard[sample][1], "nonstandard sample locations are unsupported");
|
||||
if (pixel == 0) {
|
||||
state.sampleLocations[sample] = static_cast<std::uint8_t>(bits);
|
||||
state.customSampleLocations |= x != standard[sample][0] || y != standard[sample][1];
|
||||
} else {
|
||||
Require(bits == state.sampleLocations[sample], "nonstandard sample locations varying between pixels are unsupported");
|
||||
}
|
||||
}
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
bool colorControlSupported(std::uint32_t colorControl, bool hasColorTarget) {
|
||||
|
|
@ -519,6 +528,23 @@ void intersect(VkRect2D& result, const Registers& registers, std::uint32_t offse
|
|||
|
||||
}
|
||||
|
||||
std::array<VkSampleLocationEXT, 8> SampleLocations(const State& state) {
|
||||
Require(state.samples == 1 || state.samples == 2 || state.samples == 4 || state.samples == 8, "invalid sample count");
|
||||
std::array<VkSampleLocationEXT, 8> locations{};
|
||||
for (std::uint32_t sample = 0; sample < state.samples; ++sample) {
|
||||
std::array<int, 2> position{};
|
||||
if (state.customSampleLocations) {
|
||||
const auto bits = state.sampleLocations[sample];
|
||||
position = {static_cast<int>(bits & 7u) - static_cast<int>(bits & 8u), static_cast<int>((bits >> 4u) & 7u) - static_cast<int>((bits >> 4u) & 8u)};
|
||||
} else if (state.samples != 1) {
|
||||
const auto& standard = state.samples == 2 ? StandardLocations2[sample] : state.samples == 4 ? StandardLocations4[sample] : StandardLocations8[sample];
|
||||
position = {standard[0], standard[1]};
|
||||
}
|
||||
locations[sample] = {0.5f + position[0] / 16.0f, 0.5f + position[1] / 16.0f};
|
||||
}
|
||||
return locations;
|
||||
}
|
||||
|
||||
ShaderStages DecodeShaderStages(const QueueState& queue, bool vertexWorkgroup) {
|
||||
const auto value = read(queue.context, 0x2d5);
|
||||
const auto validate = [&](bool condition, const char* reason) {
|
||||
|
|
@ -634,6 +660,7 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) {
|
|||
}
|
||||
zero(cx, 0x207, ~LayerExports, "clip distances, layer, viewport or auxiliary vertex exports");
|
||||
result.samples = decodeSamples(cx);
|
||||
decodeSampleLocations(cx, result);
|
||||
{
|
||||
const auto depthControl = read(cx, 0x200);
|
||||
const auto renderControl = find(cx, 0x000);
|
||||
|
|
|
|||
|
|
@ -1747,6 +1747,18 @@ void uint16ExportTests() {
|
|||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "color export format 8");
|
||||
}
|
||||
|
||||
VkSampleCountFlags depthSampleCounts = VK_SAMPLE_COUNT_4_BIT;
|
||||
VkResult depthSampleResult = VK_SUCCESS;
|
||||
std::uint32_t depthSampleQueries = 0;
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL sampleLocationDepthProperties(VkPhysicalDevice, VkFormat format, VkImageType type, VkImageTiling tiling, VkImageUsageFlags usage, VkImageCreateFlags flags, VkImageFormatProperties* properties) {
|
||||
Require(format == VK_FORMAT_D32_SFLOAT && type == VK_IMAGE_TYPE_2D && tiling == VK_IMAGE_TILING_OPTIMAL, "sample location query changed the depth image format or type");
|
||||
Require(usage == (VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT) && flags == VK_IMAGE_CREATE_SAMPLE_LOCATIONS_COMPATIBLE_DEPTH_BIT_EXT, "sample location query differs from depth image creation");
|
||||
++depthSampleQueries;
|
||||
properties->sampleCounts = depthSampleCounts;
|
||||
return depthSampleResult;
|
||||
}
|
||||
|
||||
void multisampleTests() {
|
||||
using AgcDriver::Graphics::ColorMetadataPass;
|
||||
using AgcDriver::Graphics::DecodeColorMetadataPass;
|
||||
|
|
@ -1755,13 +1767,19 @@ void multisampleTests() {
|
|||
auto queue = makeState();
|
||||
queue.context[0x2f8] = 0x00100001;
|
||||
queue.context[0x292] = 3;
|
||||
queue.context[0x3b8] = 0x0dc00000u;
|
||||
for (std::uint32_t pixel = 0; pixel < 4; ++pixel) queue.context[0x2fe + pixel * 4u] = 0xcc44;
|
||||
queue.context[0x31d] = 0x9000;
|
||||
queue.context[0x3b8] |= (0x18u << 19u) | 0x4000000u;
|
||||
queue.context[0x1b3] = 2;
|
||||
queue.context[0x1b4] = 2;
|
||||
return queue;
|
||||
};
|
||||
const auto cmaskAddress = reinterpret_cast<std::uintptr_t>(cmaskMemory.data());
|
||||
const auto setCmaskAddress = [&](auto& target, std::uint32_t slot = 0u) {
|
||||
const auto stride = slot * 0xfu;
|
||||
target.context[0x31f + stride] = static_cast<std::uint32_t>(cmaskAddress >> 8u);
|
||||
target.context[0x398 + slot] = static_cast<std::uint32_t>(cmaskAddress >> 40u);
|
||||
};
|
||||
auto queue = multisampled();
|
||||
auto state = DecodeState(queue);
|
||||
Require(state.samples == 2 && state.colors.size() == 1 && state.colors[0].samples == 2 && state.colors[0].cmaskAddress == 0, "2x multisampling did not decode");
|
||||
|
|
@ -1778,6 +1796,75 @@ void multisampleTests() {
|
|||
}
|
||||
state = DecodeState(queue);
|
||||
Require(state.samples == 8 && state.colors.size() == 1 && state.colors[0].samples == 8, "the standard 8x pattern (sample 7 at (7, -7), PPSA99902's registers) did not decode as standard");
|
||||
queue.context[0x2f8] = 0x00200002;
|
||||
queue.context[0x31d] = 0x12000;
|
||||
for (std::uint32_t pixel = 0; pixel < 4; ++pixel) queue.context[0x2fe + pixel * 4u] = 0x22eea66a;
|
||||
state = DecodeState(queue);
|
||||
Require(state.samples == 4 && state.customSampleLocations, "uniform custom sample locations were not decoded");
|
||||
const auto locations = AgcDriver::Graphics::SampleLocations(state);
|
||||
const std::array<VkSampleLocationEXT, 4> expected{{{0.125f, 0.875f}, {0.875f, 0.125f}, {0.375f, 0.375f}, {0.625f, 0.625f}}};
|
||||
for (std::size_t sample = 0; sample < expected.size(); ++sample) Require(locations[sample].x == expected[sample].x && locations[sample].y == expected[sample].y, "custom sample coordinates or indices changed");
|
||||
AgcDriver::Graphics::Context sampleContext{};
|
||||
expectFailure([&] { AgcDriver::Graphics::ValidateSampleLocations(sampleContext, state); }, "VK_EXT_sample_locations");
|
||||
sampleContext.sampleLocationSampleCounts = VK_SAMPLE_COUNT_2_BIT;
|
||||
expectFailure([&] { AgcDriver::Graphics::ValidateSampleLocations(sampleContext, state); }, "sample count");
|
||||
sampleContext.sampleLocationSampleCounts |= VK_SAMPLE_COUNT_4_BIT;
|
||||
AgcDriver::Graphics::ValidateSampleLocations(sampleContext, state);
|
||||
sampleContext.imageFormatProperties = sampleLocationDepthProperties;
|
||||
using AgcDriver::Graphics::SampleLocationsCompatibleDepth;
|
||||
Require(SampleLocationsCompatibleDepth(sampleContext, VK_FORMAT_D32_SFLOAT, 4), "supported custom sample depth format was refused");
|
||||
state.depth = AgcDriver::Graphics::DepthTarget{};
|
||||
state.depth->format = VK_FORMAT_D32_SFLOAT;
|
||||
state.depth->samples = 4;
|
||||
Require(AgcDriver::Graphics::ValidateSampleLocations(sampleContext, state), "supported custom depth sample locations were disabled");
|
||||
depthSampleCounts = VK_SAMPLE_COUNT_2_BIT;
|
||||
Require(!SampleLocationsCompatibleDepth(sampleContext, VK_FORMAT_D32_SFLOAT, 4), "unsupported custom sample depth count was accepted");
|
||||
expectFailure([&] { AgcDriver::Graphics::ValidateSampleLocations(sampleContext, state); }, "compatible depth format and sample count");
|
||||
depthSampleCounts = VK_SAMPLE_COUNT_4_BIT;
|
||||
depthSampleResult = VK_ERROR_FORMAT_NOT_SUPPORTED;
|
||||
Require(!SampleLocationsCompatibleDepth(sampleContext, VK_FORMAT_D32_SFLOAT, 4), "unsupported custom sample depth format was accepted");
|
||||
expectFailure([&] { AgcDriver::Graphics::ValidateSampleLocations(sampleContext, state); }, "compatible depth format and sample count");
|
||||
state.customSampleLocations = false;
|
||||
Require(!AgcDriver::Graphics::ValidateSampleLocations(sampleContext, state), "standard locations did not fall back for an incompatible depth format");
|
||||
depthSampleResult = VK_ERROR_OUT_OF_HOST_MEMORY;
|
||||
expectFailure([&] { SampleLocationsCompatibleDepth(sampleContext, VK_FORMAT_D32_SFLOAT, 4); }, "Vulkan result");
|
||||
depthSampleResult = VK_SUCCESS;
|
||||
const auto queries = depthSampleQueries;
|
||||
Require(!SampleLocationsCompatibleDepth({}, VK_FORMAT_D32_SFLOAT, 4) && !SampleLocationsCompatibleDepth(sampleContext, VK_FORMAT_D32_SFLOAT, 1), "sample location depth compatibility requires the extension and multisampling");
|
||||
Require(depthSampleQueries == queries, "ineligible sample location depth formats were queried");
|
||||
for (std::uint32_t pixel = 0; pixel < 4; ++pixel) queue.context[0x2fe + pixel * 4u] = 0x622ae6ae;
|
||||
state = DecodeState(queue);
|
||||
Require(!state.customSampleLocations, "standard sample locations require the extension");
|
||||
const auto standard = AgcDriver::Graphics::SampleLocations(state);
|
||||
const std::array<VkSampleLocationEXT, 4> expectedStandard{{{0.375f, 0.125f}, {0.875f, 0.375f}, {0.125f, 0.625f}, {0.625f, 0.875f}}};
|
||||
for (std::size_t sample = 0; sample < expectedStandard.size(); ++sample) Require(standard[sample].x == expectedStandard[sample].x && standard[sample].y == expectedStandard[sample].y, "standard sample coordinate scale or Y direction changed");
|
||||
AgcDriver::Graphics::ValidateSampleLocations({}, state);
|
||||
for (const auto samples : {2u, 8u}) {
|
||||
queue = multisampled();
|
||||
const auto log2 = samples == 2 ? 1u : 3u;
|
||||
queue.context[0x2f8] = (log2 << 20u) | log2;
|
||||
queue.context[0x31d] = (log2 << 12u) | (log2 << 15u);
|
||||
for (std::uint32_t pixel = 0; pixel < 4; ++pixel) {
|
||||
queue.context[0x2fe + pixel * 4u] = 0x77887788;
|
||||
queue.context[0x2ff + pixel * 4u] = 0x77887788;
|
||||
}
|
||||
state = DecodeState(queue);
|
||||
Require(state.customSampleLocations && state.samples == samples, "a custom 2x or 8x pattern did not decode");
|
||||
const auto endpoints = AgcDriver::Graphics::SampleLocations(state);
|
||||
for (std::uint32_t sample = 0; sample < samples; ++sample) {
|
||||
const auto expected = sample % 2u == 0 ? 0.0f : 15.0f / 16.0f;
|
||||
Require(endpoints[sample].x == expected && endpoints[sample].y == expected, "signed sample coordinate endpoints changed");
|
||||
}
|
||||
}
|
||||
queue.context[0x2f8] = 0x00300003;
|
||||
queue.context[0x31d] = 0x1b000;
|
||||
for (std::uint32_t pixel = 0; pixel < 4; ++pixel) {
|
||||
queue.context[0x2fe + pixel * 4u] = 0xbd153fd1u;
|
||||
queue.context[0x2ff + pixel * 4u] = 0x9773f95bu;
|
||||
}
|
||||
state = DecodeState(queue);
|
||||
Require(state.samples == 8 && !state.customSampleLocations && state.colors.size() == 1 && state.colors[0].samples == 8, "the standard 8x pattern (sample 7 at (7, -7), PPSA99902's registers) did not decode as standard");
|
||||
AgcDriver::Graphics::ValidateSampleLocations({}, state);
|
||||
queue = multisampled();
|
||||
queue.context[0x31d] = 0x1000;
|
||||
expectFailure([&] { DecodeState(queue); }, "fewer fragments than samples");
|
||||
|
|
@ -1791,28 +1878,28 @@ void multisampleTests() {
|
|||
expectFailure([&] { DecodeState(queue); }, "coverage conversion");
|
||||
queue = multisampled();
|
||||
queue.context[0x31c] |= 0x6000;
|
||||
queue.context[0x31f] = 0x1234;
|
||||
Require(DecodeState(queue).colors[0].cmaskAddress == 0x123400, "the CMASK address of a multisampled target did not decode");
|
||||
setCmaskAddress(queue);
|
||||
Require(DecodeState(queue).colors[0].cmaskAddress == cmaskAddress, "the CMASK address of a multisampled target did not decode");
|
||||
queue = makeState();
|
||||
queue.context[0x31c] |= 0x4000;
|
||||
expectFailure([&] { DecodeState(queue); }, "color compression");
|
||||
queue = multisampled();
|
||||
queue.context[0x31c] |= 0x2000;
|
||||
queue.context[0x31f] = 0x1234;
|
||||
setCmaskAddress(queue);
|
||||
const auto fastCleared = DecodeState(queue).colors[0];
|
||||
Require(fastCleared.cmaskAddress == 0x123400 && fastCleared.cmaskFastClear && fastCleared.cmaskBytes == 0x1000, "a multisampled FAST_CLEAR target did not decode its CMASK at the metablock size");
|
||||
Require(fastCleared.cmaskAddress == cmaskAddress && fastCleared.cmaskFastClear && fastCleared.cmaskBytes == 0x1000, "a multisampled FAST_CLEAR target did not decode its CMASK at the metablock size");
|
||||
queue = multisampled();
|
||||
queue.context[0x31c] |= 0x4000;
|
||||
queue.context[0x31f] = 0x1234;
|
||||
setCmaskAddress(queue);
|
||||
const auto compressed = DecodeState(queue).colors[0];
|
||||
Require(compressed.cmaskAddress == 0x123400 && !compressed.cmaskFastClear && compressed.cmaskBytes == 0x1000, "a multisampled COMPRESSION target did not decode its CMASK at the metablock size");
|
||||
Require(compressed.cmaskAddress == cmaskAddress && !compressed.cmaskFastClear && compressed.cmaskBytes == 0x1000, "a multisampled COMPRESSION target did not decode its CMASK at the metablock size");
|
||||
queue = multisampled();
|
||||
queue.context[0x31c] |= 0x2000;
|
||||
queue.context[0x31f] = 0;
|
||||
expectFailure([&] { DecodeState(queue); }, "without a CMASK address");
|
||||
queue = multisampled();
|
||||
queue.context[0x31c] |= 0x2000;
|
||||
queue.context[0x31f] = 0x1234;
|
||||
setCmaskAddress(queue);
|
||||
std::vector<AgcDriver::Graphics::RegisterRead> reads;
|
||||
AgcDriver::Graphics::RegisterReadLog() = &reads;
|
||||
static_cast<void>(DecodeState(queue));
|
||||
|
|
@ -1826,6 +1913,8 @@ void multisampleTests() {
|
|||
queue.context[0x32c] = 0;
|
||||
queue.context[0x3b1] = queue.context[0x3b0];
|
||||
queue.context[0x3b9] = queue.context[0x3b8];
|
||||
setCmaskAddress(queue);
|
||||
setCmaskAddress(queue, 1);
|
||||
auto pass = DecodeColorMetadataPass(queue);
|
||||
Require(pass.has_value() && pass->mode == ColorMetadataPass::Mode::Resolve && pass->source.has_value() && pass->source->samples == 2 && pass->targets.size() == 1 && pass->targets[0].samples == 1, "a CB resolve of a multisampled target did not decode");
|
||||
queue.context[0x32b] = queue.context[0x31c] ^ (1u << 8u);
|
||||
|
|
@ -1838,8 +1927,7 @@ void multisampleTests() {
|
|||
queue.context[0x3b1] = queue.context[0x3b0];
|
||||
queue.context[0x3b9] = queue.context[0x3b8];
|
||||
queue.context[0x32b] = queue.context[0x31c] | 0x2000u;
|
||||
queue.context[0x32e] = static_cast<std::uint32_t>(reinterpret_cast<std::uintptr_t>(cmaskMemory.data()) >> 8u);
|
||||
queue.context[0x399] = static_cast<std::uint32_t>(reinterpret_cast<std::uintptr_t>(cmaskMemory.data()) >> 40u);
|
||||
setCmaskAddress(queue, 1);
|
||||
expectFailure([&] { DecodeColorMetadataPass(queue); }, "fast-clear color target");
|
||||
queue = multisampled();
|
||||
queue.context[0x2f8] = 0;
|
||||
|
|
|
|||
|
|
@ -5331,6 +5331,37 @@ void pipelineLibraryTests(const Device& device) {
|
|||
state.samples = 1;
|
||||
state.colors.front().samples = 1;
|
||||
Require(lookup() != multisampled && counters().built == std::array<std::uint64_t, 4>{2, 2, 4, 4}, "the 1x pipeline after a 4x one built its libraries again");
|
||||
if ((context.sampleLocationSampleCounts & VK_SAMPLE_COUNT_4_BIT) != 0) {
|
||||
state.samples = 4;
|
||||
state.colors.front().samples = 4;
|
||||
state.customSampleLocations = true;
|
||||
state.sampleLocations = {0x10, 0x21, 0x32, 0x43};
|
||||
const auto pattern = lookup();
|
||||
const auto beforePatternChange = counters().built;
|
||||
state.sampleLocations[0] = 0x20;
|
||||
const auto otherPattern = lookup();
|
||||
Require(otherPattern != pattern && counters().built[2] == beforePatternChange[2] + 1u && counters().built[3] == beforePatternChange[3] + 1u && counters().built[0] == beforePatternChange[0] && counters().built[1] == beforePatternChange[1], "a custom sample pattern did not rebuild exactly the fragment shader and fragment output libraries");
|
||||
state.sampleLocations[0] = 0x10;
|
||||
Require(lookup() == pattern && counters().built[2] == beforePatternChange[2] + 1u && counters().built[3] == beforePatternChange[3] + 1u, "a cached custom sample pattern was not reused");
|
||||
|
||||
std::optional<VkFormat> compatibleDepth, incompatibleDepth;
|
||||
state.customSampleLocations = false;
|
||||
for (const auto format : {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D16_UNORM, VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D32_SFLOAT_S8_UINT}) {
|
||||
state.depth = DepthTarget{};
|
||||
state.depth->format = format;
|
||||
state.depth->samples = state.samples;
|
||||
(ValidateSampleLocations(context, state) ? compatibleDepth : incompatibleDepth) = format;
|
||||
}
|
||||
if (compatibleDepth && incompatibleDepth) {
|
||||
state.depth->format = *compatibleDepth;
|
||||
const auto compatible = lookup();
|
||||
const auto beforeDepthChange = counters().built;
|
||||
state.depth->format = *incompatibleDepth;
|
||||
const auto incompatible = lookup();
|
||||
Require(incompatible != compatible && counters().built[2] == beforeDepthChange[2] + 1u && counters().built[3] == beforeDepthChange[3] + 1u, "depth-format sample-location enablement did not rebuild both fragment libraries");
|
||||
}
|
||||
state.customSampleLocations = true;
|
||||
}
|
||||
WaitForOptimizedPipelines(context.device);
|
||||
const auto lto = std::getenv("APS5_NO_PIPELINE_LTO") == nullptr && context.pipelineLinkTimeOptimization;
|
||||
Require(counters().optimized == (lto ? counters().linked : 0u), "a linked pipeline was not optimized in the background");
|
||||
|
|
|
|||
|
|
@ -557,6 +557,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [Multisampled CMASK state](../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) (libSceAgcDriver) - a multisampled target has no guest texels, so its CMASK state comes from fills rather than from a read at each draw as a single-sample target's does. A fill of the whole CMASK with 0 clears the host image at the target's next use, one with 0xffffffff marks it expanded, and any other pattern, or a fill that covers only part of the CMASK, throws at the next use (inferred: main's rule that a mixed CMASK is unmodeled, applied to the one host image). A FAST_CLEAR target starts from its CMASK keys at its first use, and a COMPRESSION-only target starts expanded. Copies by DMA into the CMASK are not seen, and a later use with another CMASK address throws. The clear does not rewrite the CMASK bytes as expanded, so a new target over the same CMASK (a different extent, so a different image) seeds from them again and clears once more.
|
||||
- [Multisampled targets in CB passes](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - a CB eliminate or DCC pass over a multisampled target throws, since its texels are on the host and only a CB resolve is modeled. A CB resolve into a fast-clear destination throws: the resolve writes the destination's texels but not its CMASK, which would still read as cleared.
|
||||
- [Multisampled target guest access](../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) (libSceAgcDriver) - the memory a multisampled color target throws on (CPU read, DMA copy, texture or storage image read) is taken as `max(CB bytes * samples, width * height * element bytes * samples)` from its address, an upper bound on the sample planes of a target laid out in 64 KiB blocks (inferred: the single-sample layout size times the sample count; the exact fragment-aware footprint of addrlib is not computed, so a few bytes past the real footprint can throw too). Targets are kept per device for the device's lifetime, one per surface address, format, extent and sample count, and are not evicted.
|
||||
- [Custom MSAA sample locations](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) support a uniform 1x1 pattern for 2, 4 or 8 samples through VK_EXT_sample_locations. Patterns varying across the guest's 2x2 pixel grid throw. Devices without the required sample count, four subpixel bits or the full guest coordinate range refuse custom patterns. Depth images remain in GENERAL between draws, and render passes cannot continue across pattern changes. Guest offsets retain the existing decoder's signed four-bit, sixteenth-pixel interpretation; PS5 coverage and interpolation equivalence have not been measured on hardware.
|
||||
- [DCC display buffers](../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) are presented only when all keys of the console's DCC extent agree on uncompressed or a fast-clear code, padding keys included; other keys, and register-clear keys over a pending image, throw.
|
||||
- [Storage image write-back](../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver) stores the GPU results over a CPU-stamped range whose guest bytes equal the copy taken when the image's layers got their generation (images up to 1 MiB whose guest bytes were settled at that point, with no unit shadow, pending image results or recorded GPU store over them; other images keep the previous rule: the CPU's bytes win). A CPU store that rewrites the baseline value after the GPU changed it is not told apart from no store, and the GPU result wins there. The 1 MiB limit is a choice, not a console value.
|
||||
- [16-bit float display buffers](../../core/libs/prx/libSceAgcDriver/Execution/src/DisplayBuffer.cpp) (libSceAgcDriver, libSceVideoOut) - pixel formats `0xC001000622000000` and `0xC001000600000000` are taken as R16G16B16A16 and B16G16R16A16 float, eight bytes per texel, as [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/59183dbfa81ed7efdff6069177fed50de0552725/src/graphics/host_gpu/renderer/image/imageInfo.h) maps them and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/VideoOut/VideoOutTypes.cs) names them (`Rgba16Float`, `Bgra16Float`); PPSA11386 registers 3840x2160 tiled buffers of the first. The tiled layout is assumed to be the 8-byte SW_64KB_R_X equation of `TextureSwizzleEquations.hpp` applied to full coordinates, as addrlib applies it, by analogy with the 32-bit scanout; `ColorTargetLayout` applies the equations to in-block coordinates, which for 8-byte texels differs in every odd 64-row block row (address bit 11 takes y bit 6) and for 16-byte texels also differs, so a float display buffer written through `WriteColorTarget` and presented from guest memory would show those rows misplaced. The values are assumed to be linear light and are encoded with the sRGB curve for the 8-bit output. Bits 32-39 of these formats are `0x06`, as in the 10-bit formats SharpProspero lists without sRGB transfer, while its sRGB formats have `0x00`, its 10-bit PQ formats `0x04` (with `0x07` in bits 40-47) and its float BT.2100 PQ formats `0x07`: they show only that these formats are not the sRGB ones. NaN and negative values give 0, values of 1 and above give 255, alpha is linear. Neither the layout nor the transfer is verified against console or title frames, and HDR output is not modelled. DCC metadata on a float buffer throws.
|
||||
|
|
|
|||
Loading…
Reference in New Issue