blob: 67028c80f440e85122189121c867ae8f4afdfbc1 [file] [log] [blame] [edit]
//
// Copyright 2026 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// DriverUniforms.h:
// Defines the interface for DriverUniforms
//
#ifndef LIBANGLE_RENDERER_VULKAN_DRIVER_UNIFORMS_H_
#define LIBANGLE_RENDERER_VULKAN_DRIVER_UNIFORMS_H_
#ifdef UNSAFE_BUFFERS_BUILD
# pragma allow_unsafe_buffers
#endif
#include "GLSLANG/ShaderLang.h"
#include "common/PackedEnums.h"
#include "common/angleutils.h"
#include "libANGLE/RefCountObject.h"
#include "libANGLE/angletypes.h"
#include "libANGLE/renderer/vulkan/vk_renderer.h"
namespace
{
uint32_t MakeFlipUniform(bool flipX, bool flipY, bool invertViewport)
{
// Create snorm values of either -1 or 1, based on whether flipping is enabled or not
// respectively.
constexpr uint8_t kSnormOne = 0x7F;
constexpr uint8_t kSnormMinusOne = 0x81;
// .xy are flips for the fragment stage.
uint32_t x = flipX ? kSnormMinusOne : kSnormOne;
uint32_t y = flipY ? kSnormMinusOne : kSnormOne;
// .zw are flips for the vertex stage.
uint32_t z = x;
uint32_t w = flipY != invertViewport ? kSnormMinusOne : kSnormOne;
return x | y << 8 | z << 16 | w << 24;
}
} // namespace
namespace rx
{
template <typename OffsetsArrayT>
void UpdateAtomicCounterBufferOffset(vk::Renderer *renderer,
size_t atomicCounterBufferCount,
const gl::BufferVector &atomicCounterBuffers,
OffsetsArrayT &offsetsOut)
{
const VkDeviceSize offsetAlignment =
renderer->getPhysicalDeviceProperties().limits.minStorageBufferOffsetAlignment;
ASSERT(atomicCounterBufferCount <= offsetsOut.size() * 4);
for (uint32_t bufferIndex = 0; bufferIndex < atomicCounterBufferCount; ++bufferIndex)
{
uint32_t offsetDiff = 0;
const gl::OffsetBindingPointer<gl::Buffer> *atomicCounterBuffer =
&atomicCounterBuffers[bufferIndex];
if (atomicCounterBuffer->get())
{
VkDeviceSize offset = atomicCounterBuffer->getOffset();
VkDeviceSize alignedOffset = (offset / offsetAlignment) * offsetAlignment;
// GL requires the atomic counter buffer offset to be aligned with uint.
ASSERT((offset - alignedOffset) % sizeof(uint32_t) == 0);
offsetDiff = static_cast<uint32_t>((offset - alignedOffset) / sizeof(uint32_t));
// We expect offsetDiff to fit in an 8-bit value. The maximum difference is
// minStorageBufferOffsetAlignment / 4, where minStorageBufferOffsetAlignment
// currently has a maximum value of 256 on any device.
ASSERT(offsetDiff < (1 << 8));
}
// The output array is already cleared prior to this call.
ASSERT(bufferIndex % 4 != 0 || offsetsOut[bufferIndex / 4] == 0);
offsetsOut[bufferIndex / 4] |= static_cast<uint8_t>(offsetDiff) << ((bufferIndex % 4) * 8);
}
}
class GraphicsDriverUniforms
{
public:
GraphicsDriverUniforms(vk::Renderer *renderer)
: mAllDirtyBits({DIRTY_BIT_ATOMIC_COUNTER_BUFFER, DIRTY_BIT_DEPTH_RANGE,
DIRTY_BIT_RENDER_AREA, DIRTY_BIT_FLIP_XY, DIRTY_BIT_MISC})
{
std::fill(mUniformData.depthRange.begin(), mUniformData.depthRange.end(), 0.0f);
mUniformData.renderArea = 0;
mUniformData.flipXY = 0;
mUniformData.uint32Misc = 0;
mUniformData.dither = 0;
std::fill(mUniformData.acbBufferOffsets.begin(), mUniformData.acbBufferOffsets.end(), 0);
if (renderer->getFeatures().emulateDithering.enabled)
{
mAllDirtyBits.set(DIRTY_BIT_EMULATED_DITHER_CONTROL);
}
if (renderer->getFeatures().emulateTransformFeedback.enabled)
{
mAllDirtyBits.set(DIRTY_BIT_EMULATED_TRANSFORM_FEEDBACK);
}
mDirtyBits = mAllDirtyBits;
}
void updateDepthRange(float nearPlane, float farPlane)
{
mUniformData.depthRange = {nearPlane, farPlane};
mDirtyBits.set(DIRTY_BIT_DEPTH_RANGE);
}
bool updateRenderArea(int width, int height)
{
static_assert(gl::IMPLEMENTATION_MAX_FRAMEBUFFER_SIZE <= 0xFFFF,
"Not enough bits for render area");
static_assert(gl::IMPLEMENTATION_MAX_RENDERBUFFER_SIZE <= 0xFFFF,
"Not enough bits for render area");
uint32_t prevRenderArea = mUniformData.renderArea;
uint16_t renderAreaWidth, renderAreaHeight;
SetBitField(renderAreaWidth, width);
SetBitField(renderAreaHeight, height);
mUniformData.renderArea = renderAreaHeight << 16 | renderAreaWidth;
if (prevRenderArea == mUniformData.renderArea)
{
// No change.
return false;
}
mDirtyBits.set(DIRTY_BIT_RENDER_AREA);
return true;
}
bool updateflipXY(SurfaceRotation rotation,
bool viewportFlipped,
uint32_t numSamples,
uint32_t layeredFramebuffer)
{
bool dirty = false;
bool flipX = false;
bool flipY = false;
// Y-axis flipping only comes into play with the default framebuffer (i.e. a swapchain
// image). For 0-degree rotation, an FBO or pbuffer could be the draw framebuffer, and so we
// must check whether flipY should be positive or negative. All other rotations, will be to
// the default framebuffer, and so the value of isViewportFlipEnabledForDrawFBO() is assumed
// true; the appropriate flipY value is chosen such that gl_FragCoord is positioned at the
// lower-left corner of the window.
switch (rotation)
{
case SurfaceRotation::Identity:
flipY = viewportFlipped;
break;
case SurfaceRotation::Rotated90Degrees:
ASSERT(viewportFlipped);
break;
case SurfaceRotation::Rotated180Degrees:
ASSERT(viewportFlipped);
flipX = true;
break;
case SurfaceRotation::Rotated270Degrees:
ASSERT(viewportFlipped);
flipX = true;
flipY = true;
break;
default:
UNREACHABLE();
break;
}
uint32_t flipXY = MakeFlipUniform(flipX, flipY, viewportFlipped);
if (flipXY != mUniformData.flipXY)
{
mUniformData.flipXY = flipXY;
mDirtyBits.set(DIRTY_BIT_FLIP_XY);
dirty = true;
}
const uint32_t prevUint32Misc = mUniformData.uint32Misc;
const uint32_t swapXY = IsRotatedAspectRatio(rotation);
SetBitField(mUniformData.misc.swapXY, swapXY);
SetBitField(mUniformData.misc.numSamples, numSamples);
SetBitField(mUniformData.misc.layeredFramebuffer, layeredFramebuffer);
if (prevUint32Misc != mUniformData.uint32Misc)
{
mDirtyBits.set(DIRTY_BIT_MISC);
dirty = true;
}
return dirty;
}
void updateAtomicCounterBufferOffset(vk::Renderer *renderer,
size_t atomicCounterBufferCount,
const gl::BufferVector &atomicCounterBuffers)
{
UpdateAtomicCounterBufferOffset(renderer, atomicCounterBufferCount, atomicCounterBuffers,
mUniformData.acbBufferOffsets);
mDirtyBits.set(DIRTY_BIT_ATOMIC_COUNTER_BUFFER);
}
void updateEmulatedDitherControl(uint32_t emulatedDitherControl)
{
mUniformData.dither = emulatedDitherControl;
mDirtyBits.set(DIRTY_BIT_EMULATED_DITHER_CONTROL);
}
void updateAdvancedBlendEquation(uint32_t advancedBlendEquation)
{
SetBitField(mUniformData.misc.advancedBlendEquation, advancedBlendEquation);
mDirtyBits.set(DIRTY_BIT_MISC);
}
void updateEnabledClipDistances(uint32_t enabledClipDistances)
{
SetBitField(mUniformData.misc.clipDistancesEnabledMask, enabledClipDistances);
mDirtyBits.set(DIRTY_BIT_MISC);
}
void updateTransformDepth(uint32_t transformDepth)
{
SetBitField(mUniformData.misc.transformDepth, transformDepth);
mDirtyBits.set(DIRTY_BIT_MISC);
}
std::array<int32_t, 4> &updateTransformFeedbackData(int32_t xfbVerticesPerInstance)
{
mUniformData.xfbVerticesPerInstance = xfbVerticesPerInstance;
mDirtyBits.set(DIRTY_BIT_EMULATED_TRANSFORM_FEEDBACK);
return mUniformData.xfbBufferOffsets;
}
void setAllDirtyBits() { mDirtyBits = mAllDirtyBits; }
// Update push constant driver uniforms.
void pushConstants(vk::Renderer *renderer,
const vk::PipelineLayout &pipelineLayout,
vk::RenderPassCommandBuffer *commandBuffer)
{
if (mDirtyBits.none())
{
return;
}
static constexpr std::array<uint32_t, DirtyBitType::EnumCount + 1> kPushConstantOffsets = {
offsetof(struct UniformData, depthRange),
offsetof(struct UniformData, renderArea),
offsetof(struct UniformData, flipXY),
offsetof(struct UniformData, misc),
offsetof(struct UniformData, dither),
offsetof(struct UniformData, acbBufferOffsets),
offsetof(struct UniformData, xfbBufferOffsets),
sizeof(struct UniformData)};
// Push constant data from first dirty bit to the last dirty bit
DirtyBitType firstDirtyBit = mDirtyBits.first();
DirtyBitType lastDirtyBit = mDirtyBits.last();
uint32_t offset = kPushConstantOffsets[firstDirtyBit];
uint32_t size = kPushConstantOffsets[lastDirtyBit + 1] - offset;
void *data = reinterpret_cast<uint8_t *>(&mUniformData) + offset;
commandBuffer->pushConstants(pipelineLayout, renderer->getSupportedVulkanShaderStageMask(),
offset, size, data);
mDirtyBits.reset();
}
uint32_t getRenderArea() const { return mUniformData.renderArea; }
static uint32_t GetMaxUniformDataSize(vk::Renderer *renderer)
{
return renderer->getFeatures().emulateTransformFeedback.enabled
? sizeof(struct UniformData)
: offsetof(struct UniformData, xfbBufferOffsets);
}
private:
enum DirtyBitType : uint8_t
{
DIRTY_BIT_DEPTH_RANGE,
DIRTY_BIT_RENDER_AREA,
DIRTY_BIT_FLIP_XY,
DIRTY_BIT_MISC,
DIRTY_BIT_EMULATED_DITHER_CONTROL,
DIRTY_BIT_ATOMIC_COUNTER_BUFFER,
DIRTY_BIT_EMULATED_TRANSFORM_FEEDBACK,
EnumCount
};
using DirtyBits = angle::PackedEnumBitSet<DirtyBitType>;
ANGLE_ENABLE_STRUCT_PADDING_WARNINGS
struct UniformData
{
// .x is near, .y is far
std::array<float, 2> depthRange;
// Used to flip gl_FragCoord. Packed uvec2
uint32_t renderArea;
// Packed vec4 of snorm8
uint32_t flipXY;
// Packing information for driver uniform's misc field:
union
{
struct
{
// - 1 bit for whether surface rotation results in swapped axes
uint32_t swapXY : 1;
static_assert(0x00000001 == sh::vk::kDriverUniformsMiscSwapXYMask);
// - 5 bits for advanced blend equation
uint32_t advancedBlendEquation : 5;
static_assert((0x0000003E >>
sh::vk::kDriverUniformsMiscAdvancedBlendEquationOffset) ==
sh::vk::kDriverUniformsMiscAdvancedBlendEquationMask);
// - 6 bits for sample count
uint32_t numSamples : 6;
static_assert((0x00000FC0 >> sh::vk::kDriverUniformsMiscSampleCountOffset) ==
sh::vk::kDriverUniformsMiscSampleCountMask);
// - 8 bits for enabled clip planes
uint32_t clipDistancesEnabledMask : 8;
static_assert((0x000FF000 >> sh::vk::kDriverUniformsMiscEnabledClipPlanesOffset) ==
sh::vk::kDriverUniformsMiscEnabledClipPlanesMask);
// - 1 bit for whether depth should be transformed to Vulkan clip space
uint32_t transformDepth : 1;
static_assert((0x00100000 >> sh::vk::kDriverUniformsMiscTransformDepthOffset) ==
sh::vk::kDriverUniformsMiscTransformDepthMask);
// - 1 bit for whether alpha to coverage is enabled
uint32_t alphaToCoverage : 1;
static_assert((0x00200000 >> sh::vk::kDriverUniformsMiscAlphaToCoverageOffset) ==
sh::vk::kDriverUniformsMiscAlphaToCoverageMask);
// - 1 bit for whether the framebuffer is layered
uint32_t layeredFramebuffer : 1;
static_assert((0x00400000 >> sh::vk::kDriverUniformsMiscLayeredFramebufferOffset) ==
sh::vk::kDriverUniformsMiscLayeredFramebufferMask);
// - 9 bits unused
uint32_t unused : 9;
} misc;
uint32_t uint32Misc;
};
// Only the lower 16 bits used
uint32_t dither;
// Contain packed 8-bit values for atomic counter buffer offsets. These offsets are within
// Vulkan's minStorageBufferOffsetAlignment limit and are used to support unaligned offsets
// allowed in GL.
std::array<uint32_t, 2> acbBufferOffsets;
// Only used when transform feedback is emulated.
std::array<int32_t, 4> xfbBufferOffsets;
int32_t xfbVerticesPerInstance;
int32_t padding[3];
} UniformData;
ANGLE_DISABLE_STRUCT_PADDING_WARNINGS
static_assert(sizeof(UniformData) % (sizeof(uint32_t) * 4) == 0,
"GraphicsDriverUniforms should be 16bytes aligned");
static_assert(angle::BitMask<uint32_t>(gl::IMPLEMENTATION_MAX_CLIP_DISTANCES) <=
sh::vk::kDriverUniformsMiscEnabledClipPlanesMask,
"Not enough bits for enabled clip planes");
// Driver uniforms are updated using push constants and Vulkan spec guarantees universal support
// for 128 bytes worth of push constants. For maximum compatibility ensure
// GraphicsDriverUniforms plus extended size are within that limit.
static_assert(sizeof(UniformData) <= 128, "Only 128 bytes are guaranteed for push constants");
struct UniformData mUniformData;
// Track which constant is dirty
DirtyBits mDirtyBits;
// All possible dirty bits. Note that depends on feature bit, it may not be all bits in the
// DirtyBits.
DirtyBits mAllDirtyBits;
};
struct ComputeDriverUniforms
{
// Atomic counter buffer offsets with the same layout as in GraphicsDriverUniforms.
std::array<uint32_t, 4> acbBufferOffsets;
};
} // namespace rx
#endif // LIBANGLE_RENDERER_VULKAN_DRIVER_UNIFORMS_H_