mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Feat] (Magma): pipeline and primitive fragment shading rate via VK_KHR_fragment_shading_rate
This commit is contained in:
@@ -1083,6 +1083,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported(),
|
||||
pVulkanRenderer && pVulkanRenderer->IsNonZeroIndirectBaseInstanceSupported(),
|
||||
m_vulkanCaps.SupportsImageCubeArray);
|
||||
// GL_EXT_fragment_shading_rate rides on VK_KHR_fragment_shading_rate's pipeline rate, and
|
||||
// its _primitive companion on the primitive rate; both only when the renderer enabled them.
|
||||
// _attachment is not advertised: no framebuffer carries a rate attachment to this backend.
|
||||
if (pVulkanRenderer != nullptr && pVulkanRenderer->GetFragmentShadingRateSupport().Pipeline) {
|
||||
m_rendererInfo.RendererGLInfo.Extensions.push_back(E_GL_EXT_fragment_shading_rate);
|
||||
if (pVulkanRenderer->GetFragmentShadingRateSupport().Primitive) {
|
||||
m_rendererInfo.RendererGLInfo.Extensions.push_back(E_GL_EXT_fragment_shading_rate_primitive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BackendObject_DirectVulkan::UpdateDynamicBackendParameters() {
|
||||
@@ -1467,5 +1476,30 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_dynamicParameters.GpuVendor = GpuVendorKind::Unknown;
|
||||
break;
|
||||
}
|
||||
|
||||
// GL_EXT_fragment_shading_rate: what the renderer enabled of VK_KHR_fragment_shading_rate.
|
||||
// The attachment rate stays off (see UpdateAdvertisedExtensions), and with it every limit
|
||||
// that only describes the attachment.
|
||||
{
|
||||
const FragmentShadingRateSupport fsr =
|
||||
pVulkanRenderer != nullptr ? pVulkanRenderer->GetFragmentShadingRateSupport() : FragmentShadingRateSupport{};
|
||||
m_dynamicParameters.SupportsFragmentShadingRate = fsr.Pipeline;
|
||||
m_dynamicParameters.SupportsPrimitiveFragmentShadingRate = fsr.Pipeline && fsr.Primitive;
|
||||
m_dynamicParameters.SupportsAttachmentFragmentShadingRate = false;
|
||||
m_dynamicParameters.FragmentShadingRateNonTrivialCombiners = fsr.NonTrivialCombiners;
|
||||
m_dynamicParameters.FragmentShadingRateWithShaderDepthStencilWrites = fsr.WithShaderDepthStencilWrites;
|
||||
m_dynamicParameters.FragmentShadingRateWithSampleMask = fsr.WithSampleMask;
|
||||
m_dynamicParameters.FragmentShadingRatePrimitiveWithMultiViewport = fsr.PrimitiveWithMultipleViewports;
|
||||
m_dynamicParameters.FragmentShadingRateAttachmentWithDefaultFramebuffer = false;
|
||||
for (SizeT i = 0; i < std::size(m_dynamicParameters.FragmentShadingRateMasks); ++i) {
|
||||
m_dynamicParameters.FragmentShadingRateMasks[i] = fsr.Pipeline ? fsr.RateMasks[i] : 0;
|
||||
}
|
||||
m_dynamicParameters.MinFragmentShadingRateAttachmentTexelWidth = 0;
|
||||
m_dynamicParameters.MinFragmentShadingRateAttachmentTexelHeight = 0;
|
||||
m_dynamicParameters.MaxFragmentShadingRateAttachmentTexelWidth = 0;
|
||||
m_dynamicParameters.MaxFragmentShadingRateAttachmentTexelHeight = 0;
|
||||
m_dynamicParameters.MaxFragmentShadingRateAttachmentTexelAspectRatio = 0;
|
||||
m_dynamicParameters.MaxFragmentShadingRateAttachmentLayers = 0;
|
||||
}
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -382,6 +382,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
payload.colorAttachmentCount,
|
||||
payload.subpass);
|
||||
|
||||
// The fragment shading rate goes last so the common prefix is the eight-state list every
|
||||
// pipeline shares; it is only present when the device enabled the pipeline rate.
|
||||
static constexpr VkDynamicState kDynamicStates[] = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR,
|
||||
@@ -390,12 +392,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VK_DYNAMIC_STATE_LINE_WIDTH,
|
||||
VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK,
|
||||
VK_DYNAMIC_STATE_STENCIL_WRITE_MASK,
|
||||
VK_DYNAMIC_STATE_STENCIL_REFERENCE
|
||||
VK_DYNAMIC_STATE_STENCIL_REFERENCE,
|
||||
VK_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR
|
||||
};
|
||||
|
||||
VkPipelineDynamicStateCreateInfo dynamicState{};
|
||||
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
|
||||
dynamicState.dynamicStateCount = static_cast<uint32_t>(std::size(kDynamicStates));
|
||||
dynamicState.dynamicStateCount =
|
||||
static_cast<uint32_t>(std::size(kDynamicStates)) - (payload.fragmentShadingRateDynamic ? 0u : 1u);
|
||||
dynamicState.pDynamicStates = kDynamicStates;
|
||||
|
||||
VkPipelineInputAssemblyStateCreateInfo ia{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
|
||||
|
||||
@@ -108,6 +108,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// The fragment module writes gl_FragDepth (SPIR-V DepthReplacing); exempts the
|
||||
// pipeline from the blended depth-write quirk (see ShouldSuppressDepthWrite).
|
||||
Bool fragmentReplacesDepth = false;
|
||||
// The device enabled VK_KHR_fragment_shading_rate's pipeline rate, so the pipeline takes
|
||||
// VK_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR and every draw sets glShadingRateEXT's rate
|
||||
// and combiner ops on the command buffer. NOT hashed: it is a device constant, the same
|
||||
// for every pipeline this renderer builds.
|
||||
Bool fragmentShadingRateDynamic = false;
|
||||
Array<VkPipelineColorBlendAttachmentState, kMaxColorAttachments> colorBlendAttachments{};
|
||||
const Vector<VkPipelineShaderStageCreateInfo>* stages = nullptr;
|
||||
// The tessellation control stage this renderer synthesized for a program that has
|
||||
|
||||
@@ -315,6 +315,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Uint32 stencilBackWriteMask = 0;
|
||||
Uint32 stencilFrontReference = 0;
|
||||
Uint32 stencilBackReference = 0;
|
||||
// glShadingRateEXT / glShadingRateCombinerOpsEXT, in RenderStateParameters' packed form
|
||||
// (MGPackShadingRate). Only pipelines built with fragmentShadingRateDynamic carry the state.
|
||||
Bool fragmentShadingRateValid = false;
|
||||
Uint8 fragmentShadingRate = 0;
|
||||
Uint8 fragmentShadingRateCombinerOp0 = 0;
|
||||
Uint8 fragmentShadingRateCombinerOp1 = 0;
|
||||
// Gate over the whole per-draw dynamic-state tail (viewport, scissor, blend
|
||||
// constants, depth bias, line width, stencil) - see ApplyDynamicDrawStateTail.
|
||||
// Every GL input of that tail lives in RenderState's value-shadowed parameters:
|
||||
@@ -508,6 +514,34 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
|
||||
}
|
||||
|
||||
// GL_EXT_fragment_shading_rate's pipeline rate and combiner ops for the draw about to be
|
||||
// recorded. The packed rate is log2(width) << 2 | log2(height), which is exactly the extent
|
||||
// VK_KHR_fragment_shading_rate takes, and the combiner op indices follow
|
||||
// VkFragmentShadingRateCombinerOpKHR's order. Combiner op 1 combines with the attachment rate,
|
||||
// which this backend does not enable, so it stays KEEP whatever the GL state holds.
|
||||
static void ShadowedSetFragmentShadingRate(VkCommandBuffer commandBuffer,
|
||||
PFN_vkCmdSetFragmentShadingRateKHR setFragmentShadingRate) {
|
||||
const RenderStateParameters& p = MG_Pipe::gPipeInputs->GetRenderStateParameters();
|
||||
auto& shadow = *g_dynamicStateShadow;
|
||||
if (shadow.fragmentShadingRateValid && shadow.fragmentShadingRate == p.ShadingRate &&
|
||||
shadow.fragmentShadingRateCombinerOp0 == p.ShadingRateCombinerOp0 &&
|
||||
shadow.fragmentShadingRateCombinerOp1 == p.ShadingRateCombinerOp1) {
|
||||
return;
|
||||
}
|
||||
shadow.fragmentShadingRateValid = true;
|
||||
shadow.fragmentShadingRate = p.ShadingRate;
|
||||
shadow.fragmentShadingRateCombinerOp0 = p.ShadingRateCombinerOp0;
|
||||
shadow.fragmentShadingRateCombinerOp1 = p.ShadingRateCombinerOp1;
|
||||
const Uint8 rate = p.ShadingRate == kMGInvalidShadingRateEncoding ? 0 : p.ShadingRate;
|
||||
const VkExtent2D fragmentSize{1u << ((rate >> 2) & 3u), 1u << (rate & 3u)};
|
||||
const VkFragmentShadingRateCombinerOpKHR combinerOps[2] = {
|
||||
p.ShadingRateCombinerOp0 <= VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MUL_KHR
|
||||
? static_cast<VkFragmentShadingRateCombinerOpKHR>(p.ShadingRateCombinerOp0)
|
||||
: VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR,
|
||||
VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR};
|
||||
setFragmentShadingRate(commandBuffer, &fragmentSize, combinerOps);
|
||||
}
|
||||
|
||||
// One viewport of the ARB_viewport_array state, mapped into Vulkan's frame. Split out of
|
||||
// ApplyGLViewportState so the multi-viewport path derives index i through EXACTLY the same
|
||||
// arithmetic as index 0 - the default-framebuffer Y-flip and pre-transform rotation
|
||||
@@ -2494,6 +2528,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
s_vkCmdWireDrawIndirectCount = nullptr;
|
||||
s_vkCmdDrawMultiEXT = nullptr;
|
||||
s_vkCmdDrawMultiIndexedEXT = nullptr;
|
||||
s_vkCmdSetFragmentShadingRateKHR = nullptr;
|
||||
m_fragmentShadingRate = {};
|
||||
|
||||
if (m_instance != VK_NULL_HANDLE && m_surface != VK_NULL_HANDLE) {
|
||||
vkDestroySurfaceKHR(m_instance, m_surface, nullptr);
|
||||
@@ -3146,6 +3182,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
.backStencilDepthFailOp = MG_Util::ConvertStencilOperationToVkEnum(backStencil.PassDepthFailOp),
|
||||
.backStencilCompareOp = MG_Util::ConvertDepthTestFuncToVkEnum(backStencil.Func),
|
||||
.fragmentReplacesDepth = programObj.fragmentReplacesDepth,
|
||||
.fragmentShadingRateDynamic = m_fragmentShadingRate.Pipeline,
|
||||
.stages = &programObj.stages,
|
||||
.vertexInputState = pipelineVertexInputState,
|
||||
.stageSpirvDigests = &programObj.stageSpirvDigests
|
||||
@@ -8063,6 +8100,84 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
}
|
||||
|
||||
// VK_KHR_fragment_shading_rate, for GL_EXT_fragment_shading_rate. pipelineFragmentShadingRate
|
||||
// is the floor (glShadingRateEXT, read back through gl_ShadingRateEXT); the primitive rate
|
||||
// (gl_PrimitiveShadingRateEXT, combiner op 0) rides along when the device has it. The
|
||||
// attachment rate is left off: no GL framebuffer carries a rate attachment to this backend
|
||||
// yet, and enabling the feature would only change what the frontend may promise.
|
||||
// The extension builds on VK_KHR_create_renderpass2, which this 1.1 device needs by name.
|
||||
m_fragmentShadingRate = {};
|
||||
VkPhysicalDeviceFragmentShadingRateFeaturesKHR fragmentShadingRateFeatures{
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_FEATURES_KHR};
|
||||
if (IsExtensionSupported(availableExtensions, VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME) &&
|
||||
(m_physicalDevice.properties.apiVersion >= VK_API_VERSION_1_2 ||
|
||||
IsExtensionSupported(availableExtensions, VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME)) &&
|
||||
getPhysicalDeviceFeatures2 != nullptr && getPhysicalDeviceProperties2 != nullptr) {
|
||||
VkPhysicalDeviceFeatures2 featureQuery{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
|
||||
featureQuery.pNext = &fragmentShadingRateFeatures;
|
||||
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
|
||||
auto getFragmentShadingRates = reinterpret_cast<PFN_vkGetPhysicalDeviceFragmentShadingRatesKHR>(
|
||||
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceFragmentShadingRatesKHR"));
|
||||
if (fragmentShadingRateFeatures.pipelineFragmentShadingRate == VK_TRUE &&
|
||||
getFragmentShadingRates != nullptr) {
|
||||
EnableOptionalDeviceExtension(availableExtensions, enabledDeviceExtensions,
|
||||
VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME);
|
||||
EnableOptionalDeviceExtension(availableExtensions, enabledDeviceExtensions,
|
||||
VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME);
|
||||
fragmentShadingRateFeatures.attachmentFragmentShadingRate = VK_FALSE;
|
||||
fragmentShadingRateFeatures.pNext = const_cast<void*>(deviceCreateInfo.pNext);
|
||||
deviceCreateInfo.pNext = &fragmentShadingRateFeatures;
|
||||
|
||||
VkPhysicalDeviceFragmentShadingRatePropertiesKHR fragmentShadingRateProperties{
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_PROPERTIES_KHR};
|
||||
VkPhysicalDeviceProperties2 propertyQuery{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2};
|
||||
propertyQuery.pNext = &fragmentShadingRateProperties;
|
||||
getPhysicalDeviceProperties2(m_physicalDevice.handle, &propertyQuery);
|
||||
|
||||
auto& support = m_fragmentShadingRate;
|
||||
support.Pipeline = true;
|
||||
support.Primitive = fragmentShadingRateFeatures.primitiveFragmentShadingRate == VK_TRUE;
|
||||
support.NonTrivialCombiners = fragmentShadingRateProperties.fragmentShadingRateNonTrivialCombinerOps;
|
||||
support.WithShaderDepthStencilWrites =
|
||||
fragmentShadingRateProperties.fragmentShadingRateWithShaderDepthStencilWrites;
|
||||
support.WithSampleMask = fragmentShadingRateProperties.fragmentShadingRateWithSampleMask;
|
||||
support.PrimitiveWithMultipleViewports =
|
||||
fragmentShadingRateProperties.primitiveFragmentShadingRateWithMultipleViewports;
|
||||
support.LayeredAttachments = fragmentShadingRateProperties.layeredShadingRateAttachments;
|
||||
support.MinAttachmentTexelWidth =
|
||||
fragmentShadingRateProperties.minFragmentShadingRateAttachmentTexelSize.width;
|
||||
support.MinAttachmentTexelHeight =
|
||||
fragmentShadingRateProperties.minFragmentShadingRateAttachmentTexelSize.height;
|
||||
support.MaxAttachmentTexelWidth =
|
||||
fragmentShadingRateProperties.maxFragmentShadingRateAttachmentTexelSize.width;
|
||||
support.MaxAttachmentTexelHeight =
|
||||
fragmentShadingRateProperties.maxFragmentShadingRateAttachmentTexelSize.height;
|
||||
support.MaxAttachmentTexelAspectRatio =
|
||||
fragmentShadingRateProperties.maxFragmentShadingRateAttachmentTexelSizeAspectRatio;
|
||||
|
||||
Uint32 rateCount = 0;
|
||||
getFragmentShadingRates(m_physicalDevice.handle, &rateCount, nullptr);
|
||||
Vector<VkPhysicalDeviceFragmentShadingRateKHR> rates(
|
||||
rateCount, VkPhysicalDeviceFragmentShadingRateKHR{
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_KHR});
|
||||
getFragmentShadingRates(m_physicalDevice.handle, &rateCount, rates.data());
|
||||
const auto log2Of = [](Uint32 size) -> Int { return size == 1 ? 0 : size == 2 ? 1 : size == 4 ? 2 : -1; };
|
||||
for (Uint32 r = 0; r < rateCount; ++r) {
|
||||
const Int w = log2Of(rates[r].fragmentSize.width);
|
||||
const Int h = log2Of(rates[r].fragmentSize.height);
|
||||
if (w < 0 || h < 0) continue; // larger than GL's 4x4 can name
|
||||
const Uint16 bit = static_cast<Uint16>(1u << ((w << 2) | h));
|
||||
for (Uint32 i = 0; i < std::size(support.RateMasks); ++i) {
|
||||
if ((rates[r].sampleCounts & (1u << i)) != 0) support.RateMasks[i] |= bit;
|
||||
}
|
||||
}
|
||||
MGLOG_I("%s: primitive=%d nonTrivialCombiners=%d "
|
||||
"rates(1x)=0x%04x rates(4x)=0x%04x",
|
||||
VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME, support.Primitive, support.NonTrivialCombiners,
|
||||
support.RateMasks[0], support.RateMasks[2]);
|
||||
}
|
||||
}
|
||||
|
||||
// Codex closeout finding 2 (cf-magma): GL 4.6 core 8.26 makes an access through an
|
||||
// invalid image unit load zero and DISCARD stores and atomics. A null storage-image
|
||||
// descriptor is that rule exactly, so the wire arm binds one for such a unit
|
||||
@@ -8197,6 +8312,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
}
|
||||
|
||||
s_vkCmdSetFragmentShadingRateKHR = nullptr;
|
||||
if (m_fragmentShadingRate.Pipeline) {
|
||||
s_vkCmdSetFragmentShadingRateKHR = reinterpret_cast<PFN_vkCmdSetFragmentShadingRateKHR>(
|
||||
vkGetDeviceProcAddr(m_device, "vkCmdSetFragmentShadingRateKHR"));
|
||||
if (s_vkCmdSetFragmentShadingRateKHR == nullptr) {
|
||||
MGLOG_W("VK_KHR_fragment_shading_rate enabled but vkCmdSetFragmentShadingRateKHR is missing, "
|
||||
"will continue without fragment shading rates!");
|
||||
m_fragmentShadingRate = {};
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve the multi-draw dispatch tiers once: device support clamped by the
|
||||
// MOBILEGL_MAGMA_MULTIDRAW_MODE preference. Requesting an unavailable tier is
|
||||
// never an error - the dispatch falls down the chain ext -> indirect -> unroll.
|
||||
|
||||
@@ -130,6 +130,28 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
};
|
||||
|
||||
// VK_KHR_fragment_shading_rate as this renderer enabled it, which is everything
|
||||
// GL_EXT_fragment_shading_rate needs to know about the device. The three feature flags are the
|
||||
// ENABLED ones; the rest are the device's properties and are meaningful only under them.
|
||||
struct FragmentShadingRateSupport {
|
||||
Bool Pipeline = false;
|
||||
Bool Primitive = false;
|
||||
Bool Attachment = false;
|
||||
Bool NonTrivialCombiners = false;
|
||||
Bool WithShaderDepthStencilWrites = false;
|
||||
Bool WithSampleMask = false;
|
||||
Bool PrimitiveWithMultipleViewports = false;
|
||||
Bool LayeredAttachments = false;
|
||||
// Per sample count 1, 2, 4 .. 64: bit (log2(w) << 2 | log2(h)) per rate the device lists
|
||||
// for that count (vkGetPhysicalDeviceFragmentShadingRatesKHR).
|
||||
Uint16 RateMasks[7] = {};
|
||||
Uint32 MinAttachmentTexelWidth = 0;
|
||||
Uint32 MinAttachmentTexelHeight = 0;
|
||||
Uint32 MaxAttachmentTexelWidth = 0;
|
||||
Uint32 MaxAttachmentTexelHeight = 0;
|
||||
Uint32 MaxAttachmentTexelAspectRatio = 0;
|
||||
};
|
||||
|
||||
class VulkanRenderer : public IBufferCopyCommandProvider,
|
||||
public FrameContext::IRecordingObserver,
|
||||
public ProgramFactory::IEvictionObserver {
|
||||
@@ -391,6 +413,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// The samplerAnisotropy device feature was granted, so GL_TEXTURE_MAX_ANISOTROPY_EXT is
|
||||
// honored rather than accepted-and-ignored.
|
||||
Bool IsSamplerAnisotropySupported() const { return m_samplerAnisotropyFeatureEnabled; }
|
||||
const FragmentShadingRateSupport& GetFragmentShadingRateSupport() const { return m_fragmentShadingRate; }
|
||||
// ARB_base_instance extends indirect command records with a non-zero firstInstance and
|
||||
// requires gl_InstanceID to remain zero-based. Vulkan needs both features to honor that
|
||||
// complete contract: one legalizes the command word, the other enables the shader rebase.
|
||||
@@ -1192,6 +1215,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// (and its multiDraw feature) is enabled; null otherwise.
|
||||
PFN_vkCmdDrawMultiEXT s_vkCmdDrawMultiEXT = nullptr;
|
||||
PFN_vkCmdDrawMultiIndexedEXT s_vkCmdDrawMultiIndexedEXT = nullptr;
|
||||
// VK_KHR_fragment_shading_rate, which backs GL_EXT_fragment_shading_rate: what was enabled
|
||||
// at device creation and what the device answers for it (see GetFragmentShadingRateSupport).
|
||||
// Null / all-false on a device without pipelineFragmentShadingRate.
|
||||
PFN_vkCmdSetFragmentShadingRateKHR s_vkCmdSetFragmentShadingRateKHR = nullptr;
|
||||
FragmentShadingRateSupport m_fragmentShadingRate{};
|
||||
|
||||
// VK_EXT_transform_feedback (GL transform feedback capture)
|
||||
Bool m_transformFeedbackFeatureEnabled = false;
|
||||
|
||||
@@ -903,6 +903,9 @@
|
||||
}
|
||||
ApplyDynamicDrawStateTail(frame, pass.extent, fbo->IsDefault,
|
||||
ResolveDrawViewportCount(programObj.writesViewportIndexBuiltin));
|
||||
if (m_fragmentShadingRate.Pipeline) {
|
||||
ShadowedSetFragmentShadingRate(frame.commandBuffer, s_vkCmdSetFragmentShadingRateKHR);
|
||||
}
|
||||
// Storage images this draw writes are what the next sampled-image barrier must cover.
|
||||
if (programObj.hasStorageImages) BumpWireImageWriteEpoch();
|
||||
if (MG_Util::PipeStats::Enabled()) MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::Draws, 1);
|
||||
|
||||
Reference in New Issue
Block a user