shader: add viewport-index exports and indexed raster state

Co-authored-by: Ettore Sartori <85788613+Ekt0re@users.noreply.github.com>
This commit is contained in:
nmzik
2026-09-06 10:16:38 +04:00
parent dcaf4205cb
commit f9f8e12c35
11 changed files with 140 additions and 84 deletions
+3 -2
View File
@@ -939,7 +939,8 @@ static bool ScissorClipRuleToIntersectionMask(uint16_t rule, uint8_t* mask) {
}
ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc,
vk::Extent2D extent) {
vk::Extent2D extent, uint32_t viewport_index) {
EXIT_IF(viewport_index >= std::size(vp.viewports));
ScissorRect screen {vp.screen_scissor_left, vp.screen_scissor_top, vp.screen_scissor_right,
vp.screen_scissor_bottom};
ScissorRect final = screen;
@@ -972,7 +973,7 @@ ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeC
final = ScissorRectIntersect(final, generic);
}
const auto& viewport = vp.viewports[0];
const auto& viewport = vp.viewports[viewport_index];
ScissorRect viewport_scissor {viewport.viewport_scissor_left, viewport.viewport_scissor_top,
viewport.viewport_scissor_right,
viewport.viewport_scissor_bottom};
+1 -1
View File
@@ -39,7 +39,7 @@ void hw_print(const CommandBuffer& buffer);
void hw_check(const CommandBuffer& buffer);
void LogDrawPhase(const char* draw_name, const char* phase);
ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc,
vk::Extent2D extent);
vk::Extent2D extent, uint32_t viewport_index);
} // namespace Libs::Graphics
@@ -573,9 +573,7 @@ void CreatePipelineInternal(
depth_clip_control.negativeOneToOne = (static_params.negative_one_to_one ? VK_TRUE : VK_FALSE);
vk::PipelineViewportStateCreateInfo viewport_state {};
viewport_state.pNext = &depth_clip_control;
viewport_state.viewportCount = 1;
viewport_state.scissorCount = 1;
viewport_state.pNext = &depth_clip_control;
vk::CullModeFlags cull_mode = vk::CullModeFlagBits::eNone;
if (static_params.cull_back) {
@@ -704,8 +702,8 @@ void CreatePipelineInternal(
depth_stencil_info.maxDepthBounds = static_params.depth_max_bounds;
const vk::DynamicState dynamic_states[] = {
vk::DynamicState::eViewport,
vk::DynamicState::eScissor,
vk::DynamicState::eViewportWithCount,
vk::DynamicState::eScissorWithCount,
vk::DynamicState::eLineWidth,
vk::DynamicState::eDepthTestEnable,
vk::DynamicState::eDepthWriteEnable,
+42 -26
View File
@@ -210,7 +210,7 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col
});
const auto extent = color.Extent();
const auto sc = calc_final_scissor(vp, ctx.GetScanModeControl(), extent);
const auto sc = calc_final_scissor(vp, ctx.GetScanModeControl(), extent, 0);
LOGF(
"DrawTargetState[%u]: frame=%d %s target=%s addr=0x%010" PRIx64
@@ -315,6 +315,7 @@ static void LogDrawInputState(const CommandBuffer& buffer, const RenderColorInfo
}
static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuffer vk_buffer,
const ShaderVertexInputInfo& vs_input_info,
const RenderColorInfo* colors, uint32_t color_count,
const RenderDepthInfo& depth) {
KYTY_PROFILER_FUNCTION();
@@ -333,31 +334,46 @@ static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuf
framebuffer_extent = {limits.maxFramebufferWidth, limits.maxFramebufferHeight};
}
const auto final_scissor = calc_final_scissor(vp, ctx.GetScanModeControl(), framebuffer_extent);
const auto& outputs = vs_input_info.stage.program->info.outputs;
const bool indexed_viewports =
std::any_of(outputs.begin(), outputs.end(), [](const auto& output) {
return output.kind == ShaderRecompiler::IR::StageOutputKind::ViewportIndex;
});
constexpr uint32_t viewport_slots = std::size(HW::ScreenViewport {}.viewports);
std::array<vk::Viewport, viewport_slots> viewports {};
std::array<vk::Rect2D, viewport_slots> scissors {};
const uint32_t viewport_count = indexed_viewports ? viewport_slots : 1;
for (uint32_t i = 0; i < viewport_count; i++) {
const auto& guest = vp.viewports[i];
auto& viewport = viewports[i];
if (ctx.GetClipControl().clip_disable) {
const auto& limits = buffer.GetGraphics().GetPhysicalDeviceProperties().limits;
viewport.width = static_cast<float>(std::min(limits.maxViewportDimensions[0], 16384u));
viewport.height = static_cast<float>(std::min(limits.maxViewportDimensions[1], 16384u));
} else {
viewport.x = guest.xoffset - guest.xscale;
viewport.y = guest.yoffset - guest.yscale;
viewport.width = guest.xscale * 2.0f;
viewport.height = guest.yscale * 2.0f;
}
viewport.minDepth =
guest.zoffset - (ctx.GetClipControl().dx_clip_space ? 0.0f : guest.zscale);
viewport.maxDepth = guest.zscale + guest.zoffset;
vk::Viewport viewport {};
if (ctx.GetClipControl().clip_disable) {
const auto& limits = buffer.GetGraphics().GetPhysicalDeviceProperties().limits;
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = static_cast<float>(std::min(limits.maxViewportDimensions[0], 16384u));
viewport.height = static_cast<float>(std::min(limits.maxViewportDimensions[1], 16384u));
} else {
viewport.x = vp.viewports[0].xoffset - vp.viewports[0].xscale;
viewport.y = vp.viewports[0].yoffset - vp.viewports[0].yscale;
viewport.width = vp.viewports[0].xscale * 2.0f;
viewport.height = vp.viewports[0].yscale * 2.0f;
const auto final_scissor =
calc_final_scissor(vp, ctx.GetScanModeControl(), framebuffer_extent, i);
auto& scissor = scissors[i];
scissor.offset = {final_scissor.left, final_scissor.top};
scissor.extent = {static_cast<uint32_t>(final_scissor.right - final_scissor.left),
static_cast<uint32_t>(final_scissor.bottom - final_scissor.top)};
if (viewport.width == 0.0f) {
// Keep empty slots at their guest index; Vulkan requires a positive viewport width.
viewport.width = 1.0f;
scissor.extent = {0, 0};
}
}
viewport.minDepth = vp.viewports[0].zoffset -
(ctx.GetClipControl().dx_clip_space ? 0.0f : vp.viewports[0].zscale);
viewport.maxDepth = vp.viewports[0].zscale + vp.viewports[0].zoffset;
vk_buffer.setViewport(0, 1, &viewport);
vk::Rect2D scissor {};
scissor.offset = {final_scissor.left, final_scissor.top};
scissor.extent = {static_cast<uint32_t>(final_scissor.right - final_scissor.left),
static_cast<uint32_t>(final_scissor.bottom - final_scissor.top)};
vk_buffer.setScissor(0, 1, &scissor);
vk_buffer.setViewportWithCount(viewport_count, viewports.data());
vk_buffer.setScissorWithCount(viewport_count, scissors.data());
float line_width = ctx.GetLineWidth();
if (line_width != 1.0f) {
@@ -1125,8 +1141,8 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
CommitIndexBuffer(vk_buffer, index_binding);
}
SetGraphicsDynamicParams(buffer, vk_buffer, state.color_info, state.color_count,
state.depth_info);
SetGraphicsDynamicParams(buffer, vk_buffer, state.vs_input_info, state.color_info,
state.color_count, state.depth_info);
LogDrawPhase(draw.name, "BeginRendering");
if (set_auto_debug) {
@@ -65,12 +65,13 @@ struct VulkanExtensions {
vk::PhysicalDeviceVulkan12Features WindowContext::RequiredVulkan12Features() noexcept {
vk::PhysicalDeviceVulkan12Features features {};
features.sType = vk::StructureType::ePhysicalDeviceVulkan12Features;
features.samplerMirrorClampToEdge = VK_TRUE;
features.timelineSemaphore = VK_TRUE;
features.shaderOutputLayer = VK_TRUE;
features.bufferDeviceAddress = VK_TRUE;
features.shaderBufferInt64Atomics = VK_TRUE;
features.sType = vk::StructureType::ePhysicalDeviceVulkan12Features;
features.samplerMirrorClampToEdge = VK_TRUE;
features.timelineSemaphore = VK_TRUE;
features.shaderOutputLayer = VK_TRUE;
features.shaderOutputViewportIndex = VK_TRUE;
features.bufferDeviceAddress = VK_TRUE;
features.shaderBufferInt64Atomics = VK_TRUE;
return features;
}
@@ -276,6 +277,11 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
LOGF("shaderOutputLayer is not supported\n");
skip_device = true;
}
if (required_features12.shaderOutputViewportIndex == VK_TRUE &&
features12.shaderOutputViewportIndex != VK_TRUE) {
LOGF("shaderOutputViewportIndex is not supported\n");
skip_device = true;
}
if (required_features12.bufferDeviceAddress == VK_TRUE &&
features12.bufferDeviceAddress != VK_TRUE) {
LOGF("bufferDeviceAddress is not supported\n");
@@ -320,6 +326,10 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
LOGF("largePoints is not supported\n");
skip_device = true;
}
if (device_features2.features.multiViewport != VK_TRUE) {
LOGF("multiViewport is not supported\n");
skip_device = true;
}
if (device_features2.features.fragmentStoresAndAtomics != VK_TRUE) {
LOGF("fragmentStoresAndAtomics is not supported\n");
skip_device = true;
@@ -636,10 +646,13 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderClipDistance != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderCullDistance != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features2.features.largePoints != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features2.features.multiViewport != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderInt64 != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features2.features.vertexPipelineStoresAndAtomics != VK_TRUE);
EXIT_NOT_IMPLEMENTED(required_features12.shaderOutputLayer == VK_TRUE &&
supported_features12.shaderOutputLayer != VK_TRUE);
EXIT_NOT_IMPLEMENTED(required_features12.shaderOutputViewportIndex == VK_TRUE &&
supported_features12.shaderOutputViewportIndex != VK_TRUE);
EXIT_NOT_IMPLEMENTED(required_features12.bufferDeviceAddress == VK_TRUE &&
supported_features12.bufferDeviceAddress != VK_TRUE);
EXIT_NOT_IMPLEMENTED(required_features12.shaderBufferInt64Atomics == VK_TRUE &&
@@ -663,6 +676,7 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
device_features.shaderClipDistance = VK_TRUE;
device_features.shaderCullDistance = VK_TRUE;
device_features.largePoints = VK_TRUE;
device_features.multiViewport = VK_TRUE;
device_features.vertexPipelineStoresAndAtomics = VK_TRUE;
graphics.sample_rate_shading_enabled = true;
device_features.shaderInt64 = VK_TRUE;
@@ -338,15 +338,24 @@ void EmitAuxPositionExport(ValueEmitContext& ctx, uint32_t data, const IR::Expor
}
const auto output = IR::DecodePositionExportComponent(
state.input_info.vertex->pa_cl_vs_out_cntl, exp.index, component);
if (output.layer) {
const auto raw = ExportRawComponent(ctx, data, component);
const auto layer = state.builder.AllocateId();
state.builder.AddFunction({OpBitwiseAnd, TypeU32(state), layer, raw,
ConstantU32(state, 0x7ffu)});
const auto pointer = state.stage == ShaderType::Mesh
? MeshOutputPointer(state, IR::StageOutputKind::Layer)
: state.layer_variable;
state.builder.AddFunction({OpStore, pointer, layer});
if (output.layer || output.viewport) {
const auto raw = ExportRawComponent(ctx, data, component);
if (output.layer) {
const auto layer = state.builder.AllocateId();
state.builder.AddFunction({OpBitwiseAnd, TypeU32(state), layer, raw,
ConstantU32(state, 0x7ffu)});
const auto pointer = state.stage == ShaderType::Mesh
? MeshOutputPointer(state, IR::StageOutputKind::Layer)
: state.layer_variable;
state.builder.AddFunction({OpStore, pointer, layer});
}
if (output.viewport) {
// GFX10 MISC.z packs the viewport index in bits 16..19 alongside the layer.
const auto viewport = state.builder.AllocateId();
state.builder.AddFunction({OpBitFieldUExtract, TypeU32(state), viewport, raw,
ConstantU32(state, 16), ConstantU32(state, 4)});
state.builder.AddFunction({OpStore, state.viewport_index_variable, viewport});
}
continue;
}
if (!output.point_size && output.clip_distance == UINT32_MAX &&
@@ -52,6 +52,7 @@ enum : uint32_t {
CapabilityGroupNonUniformBallot = 64,
CapabilityGroupNonUniformShuffle = 65,
CapabilityShaderLayer = 69,
CapabilityShaderViewportIndex = 70,
CapabilitySignedZeroInfNanPreserve = 4466,
CapabilityFragmentBarycentricKHR = 5284,
CapabilityComputeDerivativeGroupQuadsKHR = 5288,
@@ -91,6 +92,7 @@ enum : uint32_t {
BuiltInClipDistance = 3,
BuiltInCullDistance = 4,
BuiltInLayer = 9,
BuiltInViewportIndex = 10,
BuiltInFragCoord = 15,
BuiltInFrontFacing = 17,
BuiltInSampleId = 18,
@@ -391,6 +393,7 @@ struct EmitterState {
uint32_t clip_distance_variable = 0;
uint32_t cull_distance_variable = 0;
uint32_t layer_variable = 0;
uint32_t viewport_index_variable = 0;
uint32_t clip_distance_count = 0;
uint32_t cull_distance_count = 0;
uint32_t depth_variable = 0;
@@ -490,6 +490,10 @@ void AllocateOutputVariables(EmitterState& state) {
case IR::StageOutputKind::Layer:
binding.variable_id = AllocateSharedOutputVariable(state, state.layer_variable);
break;
case IR::StageOutputKind::ViewportIndex:
binding.variable_id =
AllocateSharedOutputVariable(state, state.viewport_index_variable);
break;
case IR::StageOutputKind::Depth:
binding.variable_id = AllocateSharedOutputVariable(state, state.depth_variable);
break;
@@ -583,6 +587,7 @@ void AddOutputAnnotationsAndNames(EmitterState& state) {
AddBuiltIn(state.clip_distance_variable, "gl_ClipDistance", BuiltInClipDistance);
AddBuiltIn(state.cull_distance_variable, "gl_CullDistance", BuiltInCullDistance);
AddBuiltIn(state.layer_variable, "gl_Layer", BuiltInLayer);
AddBuiltIn(state.viewport_index_variable, "gl_ViewportIndex", BuiltInViewportIndex);
if (state.depth_variable != 0) {
state.builder.AddName(state.depth_variable, "gl_FragDepth");
state.builder.AddAnnotation(
@@ -712,6 +717,10 @@ void DefineModule(EmitterState& state) {
state.builder.RequireVersion(0x00010500u);
state.builder.RequireCapability(CapabilityShaderLayer);
}
if (state.viewport_index_variable != 0) {
state.builder.RequireVersion(0x00010500u);
state.builder.RequireCapability(CapabilityShaderViewportIndex);
}
if (InputVariableForKind(state, IR::StageInputKind::SampleId) != 0) {
state.builder.RequireCapability(CapabilitySampleRateShading);
}
@@ -874,6 +883,11 @@ void DefineModule(EmitterState& state) {
state.layer_variable, TypePointer(state, StorageClassOutput, TypeU32(state)),
StorageClassOutput);
}
if (state.viewport_index_variable != 0) {
state.builder.DefineGlobalVariable(
state.viewport_index_variable, TypePointer(state, StorageClassOutput, TypeU32(state)),
StorageClassOutput);
}
for (const auto& binding: state.outputs) {
if (binding.kind == IR::StageOutputKind::Parameter ||
binding.kind == IR::StageOutputKind::Mrt) {
+2 -1
View File
@@ -181,7 +181,8 @@ enum class StageOutputKind {
PointSize,
ClipDistance,
CullDistance,
Layer
Layer,
ViewportIndex
};
struct PositionExportComponent {
@@ -324,7 +324,7 @@ void CollectOutputs(const Program& program, const ShaderVertexInputInfo* vertex,
const auto output = DecodePositionExportComponent(
vertex->pa_cl_vs_out_cntl, export_info.index, component);
if (output.viewport) {
return Fail("vertex viewport-index export is unsupported");
AddOutput(info, StageOutputKind::ViewportIndex, 0, 0, "gl_ViewportIndex");
}
if (output.point_size) {
AddOutput(info, StageOutputKind::PointSize, 0, 0, "gl_PointSize");
+33 -33
View File
@@ -37,6 +37,7 @@
#include <cstdlib>
#include <cstring>
#include <deque>
#include <initializer_list>
#include <iterator>
#include <span>
#include <sstream>
@@ -907,12 +908,12 @@ SpirvStoredBuiltInElements(const std::vector<uint32_t> &binary,
return elements;
}
bool SpirvBuiltInStoreUsesAndConstant(const std::vector<uint32_t> &binary,
uint32_t builtin,
uint32_t constant) {
bool SpirvBuiltInStoreUsesOperation(const std::vector<uint32_t> &binary,
uint32_t builtin, uint32_t operation,
std::initializer_list<uint32_t> literals) {
std::unordered_set<uint32_t> variables;
std::unordered_map<uint32_t, uint32_t> constants;
std::unordered_map<uint32_t, std::array<uint32_t, 2>> bitwise_ands;
std::unordered_map<uint32_t, std::vector<uint32_t>> operations;
std::unordered_map<uint32_t, uint32_t> stores;
for (size_t i = 5; i < binary.size();) {
const uint32_t opcode = binary[i] & 0xffffu;
@@ -925,8 +926,8 @@ bool SpirvBuiltInStoreUsesAndConstant(const std::vector<uint32_t> &binary,
variables.insert(binary[i + 1]);
} else if (opcode == 43u && count >= 4u) {
constants[binary[i + 2]] = binary[i + 3];
} else if (opcode == 199u && count == 5u) {
bitwise_ands[binary[i + 2]] = {binary[i + 3], binary[i + 4]};
} else if (opcode == operation && count == 4u + literals.size()) {
operations[binary[i + 2]] = {binary.begin() + i + 4, binary.begin() + i + count};
} else if (opcode == 62u && count >= 3u) {
stores[binary[i + 1]] = binary[i + 2];
}
@@ -937,15 +938,16 @@ bool SpirvBuiltInStoreUsesAndConstant(const std::vector<uint32_t> &binary,
if (store == stores.end()) {
continue;
}
const auto value = bitwise_ands.find(store->second);
if (value == bitwise_ands.end()) {
const auto value = operations.find(store->second);
if (value == operations.end()) {
continue;
}
for (const auto operand : value->second) {
const auto literal = constants.find(operand);
if (literal != constants.end() && literal->second == constant) {
return true;
}
if (std::equal(value->second.begin(), value->second.end(), literals.begin(),
[&](uint32_t id, uint32_t expected) {
const auto literal = constants.find(id);
return literal != constants.end() && literal->second == expected;
})) {
return true;
}
}
return false;
@@ -9722,7 +9724,7 @@ void TestNewShaderRecompilerAuxPositionExports() {
const auto all_source = DisassembleSpirvBinary(all.spirv);
Check(all.spirv[1] == 0x00010500u &&
!Common::ContainsStr(all_source, "SPV_EXT_shader_viewport_index_layer") &&
SpirvBuiltInStoreUsesAndConstant(all.spirv, 9u, 0x7ffu),
SpirvBuiltInStoreUsesOperation(all.spirv, 9u, 199u, {0x7ffu}),
"layer export module version or GFX10 layer mask is incorrect");
const auto positions = std::count_if(
all.program.info.outputs.begin(), all.program.info.outputs.end(),
@@ -9776,25 +9778,23 @@ void TestNewShaderRecompilerAuxPositionExports() {
ShaderRecompiler::IR::StageOutputKind::Position,
"unmapped auxiliary position export was not ignored");
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
const auto rejects = [](uint32_t control, uint32_t en,
const char *message) {
const uint32_t shader[] = {
EncodeExp0(0x0c, 0xf, false), EncodeExp1(0, 1, 2, 3),
EncodeExp0(0x0d, en), EncodeExp1(4, 5, 6, 7),
0xbf810000u,
};
ShaderVertexInputInfo vertex{};
vertex.pa_cl_vs_out_cntl = control;
auto options = MakeCompileOptions(ShaderType::Vertex);
options.input_info.vertex = &vertex;
ExpectFatal([&] { (void)RecompileForTest(shader, options); },
message);
};
rejects(0x00280000u, 0x4u,
"viewport-index auxiliary position export did not terminate "
"compilation");
#endif
// R-Type Final 3 packs a dynamic viewport index into POS1.z bits 16..19.
// The same component can carry an independent render-target layer in bits 0..10.
for (const uint32_t control : {0x01280000u, 0x012c0000u}) {
const auto indexed = compile(unmapped, control);
CheckSpirvBinaryValidates(indexed.spirv);
Check(indexed.spirv[1] == 0x00010500u &&
SpirvContainsCapability(indexed.spirv, 70u) &&
SpirvBuiltInStoreUsesOperation(indexed.spirv, 10u, 203u, {16u, 4u}),
"viewport export did not store the GFX10 four-bit index");
const bool layered = (control & (1u << 18u)) != 0;
Check(SpirvBuiltInStoreUsesOperation(indexed.spirv, 9u, 199u, {0x7ffu}) == layered,
"packed layer/viewport export dropped or invented a layer store");
Check(std::ranges::any_of(indexed.program.info.outputs, [](const auto &output) {
return output.kind == ShaderRecompiler::IR::StageOutputKind::ViewportIndex;
}),
"viewport export was omitted from shader reflection");
}
ShaderVertexInputInfo key0{};
ShaderVertexInputInfo key1{};