Merge pull request #295 from MichonGoddijn231849/split/pixel-ancillary
fix(shader): packed pixel ancillary as SampleId and Layer, gl_Layer via Geometry, Z export formats
This commit is contained in:
commit
e2b527bb09
|
|
@ -788,6 +788,14 @@ if(BUILD_TESTING)
|
|||
set_tests_properties(image_sample_gather PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test_executable(pixel_ancillary_tests ${LIBS_INCLUDE_DIR}/tests/PixelAncillary.cpp)
|
||||
target_link_libraries(pixel_ancillary_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
configure_windows_unwind(pixel_ancillary_tests)
|
||||
add_test(NAME pixel_ancillary COMMAND pixel_ancillary_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(pixel_ancillary PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test_executable(guest_indexed_draw_tests ${LIBS_INCLUDE_DIR}/tests/GuestIndexedDraw.cpp)
|
||||
target_include_directories(guest_indexed_draw_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_indexed_draw_tests PRIVATE libSceAgc libSceAgcDriver libc)
|
||||
|
|
|
|||
|
|
@ -894,6 +894,10 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
|
|||
if (enabled.shaderImageGatherExtended) state->capabilities.push_back(spv::CapabilityImageGatherExtended);
|
||||
enabled.shaderResourceMinLod = available.shaderResourceMinLod;
|
||||
if (enabled.shaderResourceMinLod) state->capabilities.push_back(spv::CapabilityMinLod);
|
||||
enabled.sampleRateShading = available.sampleRateShading;
|
||||
enabled.geometryShader = available.geometryShader;
|
||||
if (enabled.geometryShader) state->capabilities.push_back(spv::CapabilityGeometry);
|
||||
enabled.shaderClipDistance = available.shaderClipDistance;
|
||||
if (enabled.shaderStorageImageWriteWithoutFormat) state->capabilities.push_back(spv::CapabilityStorageImageWriteWithoutFormat);
|
||||
if (enabled.shaderStorageImageReadWithoutFormat) state->capabilities.push_back(spv::CapabilityStorageImageReadWithoutFormat);
|
||||
// Bindless image tables index an image array with a wave-uniform runtime slot.
|
||||
|
|
|
|||
|
|
@ -72,6 +72,19 @@ constexpr std::uint32_t ScreenOffsetMask = ~0x01ff01ffu;
|
|||
// Bits 26/27 (ZCLIP_NEAR/FAR_DISABLE) become depth clamping; bit 19 selects the [0, 1] clip space.
|
||||
constexpr std::uint32_t ClipControlMask = ~(0x80000u | 0x01000000u | 0x0c000000u);
|
||||
|
||||
bool zFormatSupported(std::uint32_t format) {
|
||||
return format == 0u || format == 1u || format == 2u || format == 3u || format == 9u;
|
||||
}
|
||||
|
||||
std::uint32_t shaderControlMask(std::uint32_t zFormat) {
|
||||
constexpr std::uint32_t zExportEnable = 0x1u;
|
||||
constexpr std::uint32_t maskExportEnable = 0x100u;
|
||||
auto mask = ShaderControlMask;
|
||||
if (zFormat != 0u && zFormatSupported(zFormat)) mask &= ~zExportEnable;
|
||||
if (zFormat == 9u) mask &= ~maskExportEnable;
|
||||
return mask;
|
||||
}
|
||||
|
||||
// Debug aid: APS5_IGNORE_DEPTH_TEST=1 renders depth- and stencil-tested draws without a depth
|
||||
// target as if their tests always passed (wrong occlusion, but the draws run), so stages that
|
||||
// depth-test everything can exercise the draw paths before depth targets exist.
|
||||
|
|
@ -469,7 +482,7 @@ State DecodeState(const QueueState& queue) {
|
|||
Require((depthControl & 8u) == 0 || result.depth.has_value(), "depth bounds without a depth surface");
|
||||
Require((depthControl & 0xc0000000u) == 0, "depth-conditional color writes are unsupported");
|
||||
}
|
||||
zero(cx, 0x203, ShaderControlMask, "depth export, shader coverage or ordered fragment execution");
|
||||
zero(cx, 0x203, shaderControlMask(read(cx, 0x1c4)), "depth export, shader coverage or ordered fragment execution");
|
||||
zero(cx, 0x2dc, AlphaToCoverageMask, "alpha-to-coverage");
|
||||
zero(cx, 0x2f8, ~0u, "multisampling or coverage conversion");
|
||||
zero(cx, 0x292, ScanModeMask, "scan conversion mode");
|
||||
|
|
@ -515,7 +528,7 @@ State DecodeState(const QueueState& queue) {
|
|||
|
||||
// CB_COLOR_CONTROL mode 0 disables color writes, which only matters when a target is written.
|
||||
if (const auto colorControl = read(cx, 0x202); !colorControlSupported(colorControl, result.hasColorTarget)) throw std::runtime_error(colorControlMessage(colorControl));
|
||||
zero(cx, 0x1c4, ~0u, "depth or sample-mask export");
|
||||
zero(cx, 0x1c4, zFormatSupported(read(cx, 0x1c4)) ? 0u : ~0u, "depth or sample-mask export");
|
||||
const auto exportFormat = result.hasColorTarget ? read(cx, 0x1c5) : 0u;
|
||||
APS5_LOG_OUT_DEBUG("Export format=%u", exportFormat);
|
||||
// SPI_SHADER_POS_FORMAT: POS0 must be a 4-component position; later vectors carry the misc/clip
|
||||
|
|
@ -758,7 +771,9 @@ std::string DrawRejection(const QueueState& queue, bool indexed) {
|
|||
if (auto reason = require((word & 8u) == 0 || surface, "depth bounds without a depth surface"); !reason.empty()) return reason;
|
||||
if (auto reason = require((word & 0xc0000000u) == 0, "depth-conditional color writes are unsupported"); !reason.empty()) return reason;
|
||||
}
|
||||
if (auto reason = nonzero(cx, 0x203, ShaderControlMask, "depth export, shader coverage or ordered fragment execution"); !reason.empty()) return reason;
|
||||
std::uint32_t zFormat = 0;
|
||||
static_cast<void>(value(cx, 0x1c4, zFormat));
|
||||
if (auto reason = nonzero(cx, 0x203, shaderControlMask(zFormat), "depth export, shader coverage or ordered fragment execution"); !reason.empty()) return reason;
|
||||
if (auto reason = nonzero(cx, 0x2dc, AlphaToCoverageMask, "alpha-to-coverage"); !reason.empty()) return reason;
|
||||
if (auto reason = nonzero(cx, 0x2f8, ~0u, "multisampling or coverage conversion"); !reason.empty()) return reason;
|
||||
if (auto reason = nonzero(cx, 0x292, ScanModeMask, "scan conversion mode"); !reason.empty()) return reason;
|
||||
|
|
@ -775,7 +790,7 @@ std::string DrawRejection(const QueueState& queue, bool indexed) {
|
|||
if (auto reason = nonzero(cx, 0x204, ClipControlMask, "unsupported PA_CL_CLIP_CNTL flags"); !reason.empty()) return reason;
|
||||
std::uint32_t targetMask = 0, shaderMask = 0;
|
||||
if (value(cx, 0x8e, targetMask) && value(cx, 0x8f, shaderMask) && value(cx, 0x202, word) && !colorControlSupported(word, (targetMask & shaderMask) != 0)) return colorControlMessage(word);
|
||||
if (auto reason = nonzero(cx, 0x1c4, ~0u, "depth or sample-mask export"); !reason.empty()) return reason;
|
||||
if (auto reason = nonzero(cx, 0x1c4, zFormatSupported(zFormat) ? 0u : ~0u, "depth or sample-mask export"); !reason.empty()) return reason;
|
||||
if (PixelProgramUnset(queue)) return NullPixelProgramRejection(queue);
|
||||
for (const auto offset : {0x1b3u, 0x1b4u, 0x1c5u}) {
|
||||
if (find(cx, offset) != cx.end()) continue;
|
||||
|
|
|
|||
|
|
@ -582,6 +582,35 @@ void DepthStencilTests() {
|
|||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "DCC 3D color targets");
|
||||
}
|
||||
|
||||
// SPI_SHADER_Z_FORMAT (0x1c4) and the export enables of DB_SHADER_CONTROL (0x203): Z export needs a
|
||||
// format with a depth channel (1, 2, 3 or 32_ABGR 9), the sample mask needs 32_ABGR, and the
|
||||
// formats the export path does not lay out are refused. A missing 0x1c4 gives no verdict here.
|
||||
void ZExportTests() {
|
||||
constexpr std::uint32_t zExportEnable = 0x1u;
|
||||
constexpr std::uint32_t maskExportEnable = 0x100u;
|
||||
const auto withExports = [](std::uint32_t format, std::uint32_t exports) {
|
||||
auto queue = makeState();
|
||||
queue.context[0x1b3] = 2;
|
||||
queue.context[0x1b4] = 2;
|
||||
queue.context[0x1c4] = format;
|
||||
queue.context[0x203] = 0x800u | exports;
|
||||
return queue;
|
||||
};
|
||||
for (const std::uint32_t format : {1u, 2u, 3u, 9u}) {
|
||||
const auto queue = withExports(format, zExportEnable);
|
||||
Require(AgcDriver::Graphics::DrawRejection(queue, true).empty(), "a Z export with format " + std::to_string(format) + " was rejected");
|
||||
}
|
||||
Require(!AgcDriver::Graphics::DrawRejection(withExports(0, zExportEnable), true).empty(), "a Z export without a Z format was accepted");
|
||||
Require(AgcDriver::Graphics::DrawRejection(withExports(9, zExportEnable | maskExportEnable), true).empty(), "a sample-mask export with 32_ABGR was rejected");
|
||||
Require(!AgcDriver::Graphics::DrawRejection(withExports(1, zExportEnable | maskExportEnable), true).empty(), "a sample-mask export with 32_R was accepted");
|
||||
for (std::uint32_t format = 4; format <= 8; ++format) {
|
||||
Require(!AgcDriver::Graphics::DrawRejection(withExports(format, 0), true).empty(), "Z format " + std::to_string(format) + " was accepted");
|
||||
}
|
||||
auto absent = withExports(0, 0);
|
||||
absent.context.erase(0x1c4);
|
||||
Require(AgcDriver::Graphics::DrawRejection(absent, true).empty(), "a missing SPI_SHADER_Z_FORMAT was rejected (or threw) in the precheck");
|
||||
}
|
||||
|
||||
void DepthBoundsBiasTests() {
|
||||
const auto bits = [](float value) {
|
||||
std::uint32_t word = 0;
|
||||
|
|
@ -2041,6 +2070,7 @@ int main() {
|
|||
hardwareScreenOffsetTests();
|
||||
DepthClipTests();
|
||||
DepthStencilTests();
|
||||
ZExportTests();
|
||||
DepthBoundsBiasTests();
|
||||
conservativeZExportTests();
|
||||
DisabledColorTests();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
#include "IntermediateRepresentation/IrBuilder.hpp"
|
||||
#include "Optimization/ConstantFolder.hpp"
|
||||
#include "Optimization/DeadCodeEliminator.hpp"
|
||||
#include "Optimization/ShaderInfoCollector.hpp"
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
using namespace ShaderRecompiler;
|
||||
static void Require(bool value) { if (!value) throw std::runtime_error("packed pixel ancillary regression"); }
|
||||
static IrValue& Build(IrProgram& program, IrOpcode opcode, std::uint32_t offset, std::uint32_t count) {
|
||||
program.Resources().stage = IrShaderStage::Pixel;
|
||||
program.Resources().resourceTrackingComplete = true;
|
||||
auto& block = program.CreateBlock();
|
||||
program.SetEntryBlock(block);
|
||||
program.BlockOrder().push_back(&block);
|
||||
IrBuilder builder(program);
|
||||
builder.SetInsertionPoint(block);
|
||||
auto& ancillary = builder.Emit(IrOpcode::GetBuiltin, IrType::U32, {&builder.Constant(static_cast<std::uint32_t>(StageInputKind::PackedAncillary)), &builder.Constant(0u)});
|
||||
auto& user = opcode == IrOpcode::BitwiseOr32 ? builder.Emit(opcode, IrType::U32, {&ancillary, &builder.Constant(offset)})
|
||||
: builder.Emit(opcode, IrType::U32, {&ancillary, &builder.Constant(offset), &builder.Constant(count)});
|
||||
static_cast<void>(builder.Emit(IrOpcode::ReferenceU32, IrType::Void, {&user}));
|
||||
static_cast<void>(builder.Emit(IrOpcode::Return, IrType::Void, {}));
|
||||
return user;
|
||||
}
|
||||
static void Lower(IrProgram& program) {
|
||||
ConstantFolder().Fold(program);
|
||||
DeadCodeEliminator().RemoveIdentities(program);
|
||||
DeadCodeEliminator().Eliminate(program);
|
||||
const ShaderPixelInputInfo pixel {};
|
||||
ShaderInfoCollector().Collect(program, ShaderStageInputInfo {nullptr, &pixel, nullptr});
|
||||
}
|
||||
static void Extract(IrOpcode opcode, std::uint32_t offset, std::uint32_t count, StageInputKind kind, std::uint32_t fieldOffset) {
|
||||
IrProgram program;
|
||||
auto& user = Build(program, opcode, offset, count);
|
||||
Lower(program);
|
||||
const IrValue* field = user.Argument(0)->Resolve();
|
||||
Require(field->Opcode() == IrOpcode::GetBuiltin);
|
||||
Require(static_cast<StageInputKind>(field->Argument(0)->Resolve()->ImmediateU32()) == kind);
|
||||
Require(user.Argument(1)->Resolve()->ImmediateU32() == fieldOffset);
|
||||
Require(user.Argument(2)->Resolve()->ImmediateU32() == count);
|
||||
}
|
||||
static void Refused(IrOpcode opcode, std::uint32_t offset, std::uint32_t count) {
|
||||
IrProgram program;
|
||||
static_cast<void>(Build(program, opcode, offset, count));
|
||||
try {
|
||||
Lower(program);
|
||||
} catch (const std::runtime_error& error) {
|
||||
Require(std::string(error.what()).find("unsupported live use") != std::string::npos);
|
||||
return;
|
||||
}
|
||||
Require(false);
|
||||
}
|
||||
int main() {
|
||||
Extract(IrOpcode::BitFieldUExtract, 8u, 4u, StageInputKind::SampleId, 0u);
|
||||
Extract(IrOpcode::BitFieldUExtract, 9u, 2u, StageInputKind::SampleId, 1u);
|
||||
Extract(IrOpcode::BitFieldUExtract, 16u, 13u, StageInputKind::Layer, 0u);
|
||||
Extract(IrOpcode::BitFieldSExtract, 20u, 9u, StageInputKind::Layer, 4u);
|
||||
Refused(IrOpcode::BitwiseOr32, 1u, 0u);
|
||||
Refused(IrOpcode::BitFieldUExtract, 2u, 4u);
|
||||
Refused(IrOpcode::BitFieldUExtract, 10u, 4u);
|
||||
Refused(IrOpcode::BitFieldUExtract, 13u, 2u);
|
||||
Refused(IrOpcode::BitFieldUExtract, 16u, 14u);
|
||||
}
|
||||
|
|
@ -2,8 +2,11 @@
|
|||
#include "IntermediateRepresentation/IrBuilder.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace ShaderRecompiler {
|
||||
namespace {
|
||||
|
|
@ -181,12 +184,68 @@ bool foldCompositeExtract(IrBuilder& builder, IrValue& inst, IrOpcode construct,
|
|||
return false;
|
||||
}
|
||||
|
||||
void collectUses(IrValue& value, std::vector<IrUse>& uses) {
|
||||
for (const IrUse& use : value.OperandUses()) {
|
||||
if (use.user->Opcode() == IrOpcode::Identity) {
|
||||
collectUses(*use.user, uses);
|
||||
} else {
|
||||
uses.push_back(use);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool lowerPackedAncillary(IrProgram& program, IrBuilder& builder, IrValue& ancillary) {
|
||||
auto& kind = resolveArg(ancillary, 0);
|
||||
if (!isImmediate(kind, IrType::U32) || static_cast<StageInputKind>(kind.ImmediateU32()) != StageInputKind::PackedAncillary) {
|
||||
return false;
|
||||
}
|
||||
std::vector<IrUse> uses;
|
||||
collectUses(ancillary, uses);
|
||||
std::array<IrValue*, 2> fields {};
|
||||
const auto field = [&](std::size_t index) -> IrValue& {
|
||||
if (fields[index] == nullptr) {
|
||||
const auto builtin = index == 0u ? StageInputKind::SampleId : StageInputKind::Layer;
|
||||
IrValue& created = program.CreateValue(IrOpcode::GetBuiltin, IrType::U32);
|
||||
created.AddArgument(&builder.Constant(static_cast<std::uint32_t>(builtin)));
|
||||
created.AddArgument(&builder.Constant(0u));
|
||||
ancillary.Parent()->InsertInstructionBefore(&ancillary, &created);
|
||||
fields[index] = &created;
|
||||
}
|
||||
return *fields[index];
|
||||
};
|
||||
constexpr std::array<std::pair<std::uint32_t, std::uint32_t>, 2> ranges {{{8u, 4u}, {16u, 13u}}};
|
||||
bool lowered = false;
|
||||
for (const IrUse& use : uses) {
|
||||
IrValue& user = *use.user;
|
||||
if ((user.Opcode() != IrOpcode::BitFieldUExtract && user.Opcode() != IrOpcode::BitFieldSExtract) || use.operand != 0u) {
|
||||
continue;
|
||||
}
|
||||
auto& offset = resolveArg(user, 1);
|
||||
auto& count = resolveArg(user, 2);
|
||||
if (!isImmediate(offset, IrType::U32) || !isImmediate(count, IrType::U32) || count.ImmediateU32() == 0u) {
|
||||
continue;
|
||||
}
|
||||
const auto range = std::ranges::find_if(ranges, [&](const auto& candidate) {
|
||||
return offset.ImmediateU32() >= candidate.first && count.ImmediateU32() <= candidate.second &&
|
||||
offset.ImmediateU32() - candidate.first <= candidate.second - count.ImmediateU32();
|
||||
});
|
||||
if (range == ranges.end()) {
|
||||
continue;
|
||||
}
|
||||
user.ReplaceArgument(0, &field(static_cast<std::size_t>(range - ranges.begin())));
|
||||
user.ReplaceArgument(1, &builder.Constant(offset.ImmediateU32() - range->first));
|
||||
lowered = true;
|
||||
}
|
||||
return lowered;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ConstantFolder::tryFoldValue(IrProgram& program, IrValue& value) const {
|
||||
IrBuilder builder(program);
|
||||
switch (value.Opcode()) {
|
||||
case IrOpcode::Phi: return foldPhi(value);
|
||||
case IrOpcode::GetBuiltin: return lowerPackedAncillary(program, builder, value);
|
||||
case IrOpcode::SelectU1:
|
||||
case IrOpcode::SelectF32:
|
||||
case IrOpcode::SelectU32: return foldSelect(value);
|
||||
|
|
|
|||
|
|
@ -137,10 +137,13 @@ void DefineModule(SpirvEmitterState& state) {
|
|||
if (state.cullDistanceVariable != 0) {
|
||||
state.module.EmitCapability(spv::CapabilityCullDistance);
|
||||
}
|
||||
if (state.layerVariable != 0 || InputVariableForKind(state, StageInputKind::Layer) != 0) {
|
||||
if (state.layerVariable != 0) {
|
||||
state.module.RequireVersion(0x00010500u);
|
||||
state.module.EmitCapability(spv::CapabilityShaderLayer);
|
||||
}
|
||||
if (InputVariableForKind(state, StageInputKind::Layer) != 0) {
|
||||
state.module.EmitCapability(spv::CapabilityGeometry);
|
||||
}
|
||||
if (state.viewportIndexVariable != 0) {
|
||||
state.module.RequireVersion(0x00010500u);
|
||||
state.module.EmitCapability(spv::CapabilityShaderViewportIndex);
|
||||
|
|
|
|||
Loading…
Reference in New Issue