Fix separate stencil test and write values
This commit is contained in:
parent
940e42f130
commit
586cbd4a0d
|
|
@ -39,8 +39,7 @@ namespace Libs::Graphics {
|
|||
EXIT("unsupported render state; details were printed above\n");
|
||||
}
|
||||
|
||||
static vk::StencilOp ConvertStencilOp(uint8_t value, uint8_t write_mask, uint8_t op_value,
|
||||
uint8_t test_value) {
|
||||
static vk::StencilOp ConvertStencilOp(uint8_t value, uint8_t write_mask, uint8_t op_value) {
|
||||
if (write_mask == 0) {
|
||||
return vk::StencilOp::eKeep;
|
||||
}
|
||||
|
|
@ -52,11 +51,6 @@ static vk::StencilOp ConvertStencilOp(uint8_t value, uint8_t write_mask, uint8_t
|
|||
if ((op_value & write_mask) == 0) {
|
||||
return vk::StencilOp::eZero;
|
||||
}
|
||||
if (((op_value ^ test_value) & write_mask) != 0) {
|
||||
DepthFatal("unsupported stencil replacement: write mask=0x%02" PRIx8
|
||||
", operation value=0x%02" PRIx8 ", test value=0x%02" PRIx8,
|
||||
write_mask, op_value, test_value);
|
||||
}
|
||||
return vk::StencilOp::eReplace;
|
||||
case Prospero::StencilOp::kAddClamp: return vk::StencilOp::eIncrementAndClamp;
|
||||
case Prospero::StencilOp::kSubClamp: return vk::StencilOp::eDecrementAndClamp;
|
||||
|
|
@ -80,11 +74,35 @@ static vk::StencilOp ConvertStencilOp(uint8_t value, uint8_t write_mask, uint8_t
|
|||
static PipelineStencilStaticState ConvertStencilState(
|
||||
uint8_t compare, const std::array<uint8_t, 3>& operations, uint8_t op_value,
|
||||
PipelineStencilDynamicState& dynamic) {
|
||||
return {
|
||||
ConvertStencilOp(operations[0], dynamic.writeMask, op_value, dynamic.reference),
|
||||
ConvertStencilOp(operations[1], dynamic.writeMask, op_value, dynamic.reference),
|
||||
ConvertStencilOp(operations[2], dynamic.writeMask, op_value, dynamic.reference),
|
||||
static_cast<vk::CompareOp>(compare)};
|
||||
const auto test_value = dynamic.reference;
|
||||
auto reference = test_value;
|
||||
auto required_bits = dynamic.compareMask;
|
||||
if (compare == static_cast<uint8_t>(vk::CompareOp::eAlways) ||
|
||||
compare == static_cast<uint8_t>(vk::CompareOp::eNever)) {
|
||||
required_bits = 0;
|
||||
}
|
||||
std::array<vk::StencilOp, 3> converted {};
|
||||
for (size_t i = 0; i < operations.size(); i++) {
|
||||
converted[i] = ConvertStencilOp(operations[i], dynamic.writeMask, op_value);
|
||||
if (converted[i] != vk::StencilOp::eReplace) {
|
||||
continue;
|
||||
}
|
||||
auto replacement = test_value;
|
||||
if (static_cast<Prospero::StencilOp>(operations[i]) == Prospero::StencilOp::kReplaceOp) {
|
||||
replacement = op_value;
|
||||
}
|
||||
if (((reference ^ replacement) & required_bits & dynamic.writeMask) != 0) {
|
||||
DepthFatal("unsupported stencil replacement: compare=%u, compare mask=0x%02" PRIx32
|
||||
", write mask=0x%02" PRIx32 ", operation value=0x%02" PRIx8
|
||||
", test value=0x%02" PRIx32,
|
||||
compare, dynamic.compareMask, dynamic.writeMask, op_value, test_value);
|
||||
}
|
||||
// Vulkan shares one reference between comparison and every replacement on this face.
|
||||
reference = (reference & ~dynamic.writeMask) | (replacement & dynamic.writeMask);
|
||||
required_bits |= dynamic.writeMask;
|
||||
}
|
||||
dynamic.reference = reference;
|
||||
return {converted[0], converted[1], converted[2], static_cast<vk::CompareOp>(compare)};
|
||||
}
|
||||
|
||||
[[nodiscard]] static vk::Format ResolveHostDepthAttachmentFormat(const CommandBuffer& buffer,
|
||||
|
|
|
|||
|
|
@ -12289,7 +12289,7 @@ public:
|
|||
cmd.setLineWidth(1);
|
||||
cmd.setDepthTestEnable(depth.depth_test_enable);
|
||||
cmd.setDepthWriteEnable(depth.depth_write_enable);
|
||||
cmd.setDepthCompareOp(vk::CompareOp::eAlways);
|
||||
cmd.setDepthCompareOp(depth.depth_compare_op);
|
||||
cmd.setDepthBiasEnable(false);
|
||||
if (depth.stencil_test_enable) {
|
||||
const auto set_stencil = [&](vk::StencilFaceFlagBits face,
|
||||
|
|
@ -12424,15 +12424,7 @@ public:
|
|||
RenderExecutorTestAccess::ResolveRenderDepthTarget(executor, scheduler.Current(), depth);
|
||||
auto stencil_readback = CreateHostBuffer(name, extent * extent,
|
||||
vk::BufferUsageFlagBits::eTransferDst, {});
|
||||
for (const bool back_face : {false, true}) {
|
||||
const uint8_t initial = back_face ? 0xb5 : 0x35;
|
||||
const uint8_t written = back_face ? 0x35 : 0xb5;
|
||||
vk::ClearValue clear{};
|
||||
clear.depthStencil = vk::ClearDepthStencilValue{0.625f, initial};
|
||||
TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), depth.image_id,
|
||||
{vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil,
|
||||
0, 1, 0, 1}, clear);
|
||||
draw(pipeline(true, 2, 2, false, !back_face));
|
||||
const auto read_stencil = [&] {
|
||||
const vk::BufferImageCopy copy{0, 0, 0,
|
||||
{vk::ImageAspectFlagBits::eStencil, 0, 0, 1}, {}, {extent, extent, 1}};
|
||||
cache.GetImage(depth.image_id).Download(std::span{©, 1},
|
||||
|
|
@ -12447,15 +12439,63 @@ public:
|
|||
dependency.pMemoryBarriers = &barrier;
|
||||
scheduler.Current().Handle().pipelineBarrier2(dependency);
|
||||
scheduler.Finish();
|
||||
const auto result = ReadBuffer(name, stencil_readback, extent * extent / 4);
|
||||
return ReadBuffer(name, stencil_readback, extent * extent / 4);
|
||||
};
|
||||
const auto check_stencil = [&](const char *label, bool back_face,
|
||||
uint8_t initial, uint8_t written) {
|
||||
vk::ClearValue clear{};
|
||||
clear.depthStencil = vk::ClearDepthStencilValue{0.625f, initial};
|
||||
TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), depth.image_id,
|
||||
{vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil,
|
||||
0, 1, 0, 1}, clear);
|
||||
draw(pipeline(true, 2, 2, false, !back_face));
|
||||
const auto result = read_stencil();
|
||||
const auto *stencil_bytes = reinterpret_cast<const uint8_t *>(result.data());
|
||||
for (uint32_t texel = 0; texel < extent * extent; ++texel) {
|
||||
const uint8_t expected = solid_pixels[texel * 4] != 0 ? written : initial;
|
||||
Require(name, back_face ? "back-face masked ReplaceOp" : "front-face ReplaceTest",
|
||||
Require(name, label,
|
||||
stencil_bytes[texel] == expected,
|
||||
"captured stencil operations changed masked bits or triangle coverage");
|
||||
}
|
||||
}
|
||||
};
|
||||
check_stencil("front-face ReplaceTest", false, 0x35, 0xb5);
|
||||
check_stencil("back-face masked ReplaceOp", true, 0xb5, 0x35);
|
||||
|
||||
// Frame 6115 uses separate test/op replacement values with Always comparisons.
|
||||
stencil_control.z_enable = stencil_control.z_write_enable = true;
|
||||
stencil_control.zfunc = static_cast<uint8_t>(vk::CompareOp::eLessOrEqual);
|
||||
registers.SetDepthControl(stencil_control);
|
||||
registers.SetStencilControl({0, 3, 9, 0, 4, 8});
|
||||
registers.SetStencilMask({8, 0xff, 0xff, 1, 8, 0xff, 0xff, 1});
|
||||
RenderExecutorTestAccess::ResolveRenderDepthTarget(executor, scheduler.Current(), depth);
|
||||
Require(name, "independent stencil references",
|
||||
depth.stencil_dynamic_front.reference == 8 &&
|
||||
depth.stencil_dynamic_back.reference == 1,
|
||||
"always-pass faces must preserve their distinct replacement values");
|
||||
check_stencil("front test-value replacement", false, 0x35, 8);
|
||||
check_stencil("back operation-value replacement", true, 0x35, 1);
|
||||
stencil_control.zfunc = static_cast<uint8_t>(vk::CompareOp::eNever);
|
||||
registers.SetDepthControl(stencil_control);
|
||||
RenderExecutorTestAccess::ResolveRenderDepthTarget(executor, scheduler.Current(), depth);
|
||||
check_stencil("front depth-fail subtract wrap", false, 0, 0xff);
|
||||
check_stencil("back depth-fail add wrap", true, 0xff, 0);
|
||||
|
||||
// A compare on bit 3 and replacement on bit 0 share one Vulkan reference
|
||||
// without changing either native operation or the untouched stencil bits.
|
||||
stencil_control.z_enable = stencil_control.z_write_enable = false;
|
||||
stencil_control.stencilfunc = stencil_control.stencilfunc_bf =
|
||||
static_cast<uint8_t>(vk::CompareOp::eEqual);
|
||||
registers.SetDepthControl(stencil_control);
|
||||
registers.SetStencilControl({0, 4, 0, 0, 4, 0});
|
||||
registers.SetStencilMask({8, 8, 1, 1, 8, 8, 1, 1});
|
||||
RenderExecutorTestAccess::ResolveRenderDepthTarget(executor, scheduler.Current(), depth);
|
||||
Require(name, "masked compare and replace reference",
|
||||
depth.stencil_dynamic_front.reference == 9 &&
|
||||
depth.stencil_dynamic_back.reference == 9,
|
||||
"disjoint comparison and replacement bits must combine into one reference");
|
||||
check_stencil("masked compare passes", false, 0xb8, 0xb9);
|
||||
check_stencil("masked compare fails", false, 0xb0, 0xb0);
|
||||
|
||||
DestroyBuffer(&stencil_readback);
|
||||
}
|
||||
resources.UnmapMemory(depth_address, allocation_size);
|
||||
|
|
|
|||
Loading…
Reference in New Issue