blob: 61b20f4c4d794d96fd1e563a95d10ea2b2645d5c [file] [log] [blame]
/*
* Copyright 2021 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/gpu/graphite/UniformManager.h"
#include "src/gpu/graphite/PipelineData.h"
// ensure that these types are the sizes the uniform data is expecting
static_assert(sizeof(int32_t) == 4);
static_assert(sizeof(float) == 4);
static_assert(sizeof(SkHalf) == 2);
namespace skgpu::graphite {
int UniformOffsetCalculator::advanceOffset(SkSLType type, int count) {
SkASSERT(SkSLTypeCanBeUniformValue(type));
int dimension = SkSLTypeMatrixSize(type);
if (dimension > 0) {
// All SkSL matrices are square and can be interpreted as an array of column vectors
count = std::max(count, 1) * dimension;
} else {
dimension = SkSLTypeVecLength(type);
}
SkASSERT(1 <= dimension && dimension <= 4);
// Bump dimension up to 4 if the array or vec3 consumes 4 primitives per element
// NOTE: This affects the size, alignment already rounds up to a power of 2 automatically.
const bool isArray = count > Uniform::kNonArray;
if ((isArray && LayoutRules::AlignArraysAsVec4(fLayout)) ||
(dimension == 3 && (isArray || LayoutRules::PadVec3Size(fLayout)))) {
dimension = 4;
}
const int primitiveSize = LayoutRules::UseFullPrecision(fLayout) ||
SkSLTypeIsFullPrecisionNumericType(type) ? 4 : 2;
const int align = SkNextPow2(dimension) * primitiveSize;
const int alignedOffset = SkAlignTo(fOffset, align);
fOffset = alignedOffset + dimension * primitiveSize * std::max(count, 1);
fReqAlignment = std::max(fReqAlignment, align);
return alignedOffset;
}
int UniformOffsetCalculator::advanceStruct(const UniformOffsetCalculator& substruct, int count) {
SkASSERT(substruct.fLayout == fLayout); // Invalid if the layout rules used aren't consistent
// If array element strides are forced to 16-byte alignment, structs must also have their
// base alignment rounded up to 16-byte alignment, which should have been accounted for in
// 'substruct's constructor.
const int baseAlignment = substruct.requiredAlignment();
SkASSERT(!LayoutRules::AlignArraysAsVec4(fLayout) || SkIsAlign16(baseAlignment));
// Per layout rule #9, the struct size must be padded to its base alignment
// (see https://registry.khronos.org/OpenGL/specs/gl/glspec45.core.pdf#page=159).
const int alignedSize = SkAlignTo(substruct.size(), baseAlignment);
const int alignedOffset = SkAlignTo(fOffset, baseAlignment);
fOffset = alignedOffset + alignedSize * std::max(count, 1);
fReqAlignment = std::max(fReqAlignment, baseAlignment);
return alignedOffset;
}
//////////////////////////////////////////////////////////////////////////////
void UniformManager::resetWithNewLayout(Layout layout) {
fStorage.clear();
fLayout = layout;
fReqAlignment = 1;
fEndPaintAlignment = 1;
fEndPaintOffset = 0;
fNonShadingOffset = 0;
fStructBaseAlignment = 0;
fWrotePaintColor = false;
#ifdef SK_DEBUG
fOffsetCalculator = UniformOffsetCalculator::ForTopLevel(layout);
fMarkedOffsetCalculator = fOffsetCalculator;
fSubstructCalculator = {};
fExpectedUniforms = {};
fExpectedUniformIndex = 0;
#endif
}
void UniformManager::rewindToMark() {
// Prepare the storage paramters such that:
// 1) If the renderstep is shading, the size and alignment can grow directly from the state at
// at the end of the paint uniforms.
// 2) If the renderstep is non-shading, the storage can be aligned to the renderstep's
// uniform alignment requirements.
fStorage.resize(fEndPaintOffset);
fReqAlignment = fEndPaintAlignment;
fNonShadingOffset = 0;
SkDEBUGCODE(fOffsetCalculator = fMarkedOffsetCalculator);
// If we're rewinding, we shouldn't be using substructs.
SkASSERT(fSubstructStartingOffset == -1);
// Any struct should be closed.
SkASSERT(fStructBaseAlignment == 0);
}
static std::pair<SkSLType, int> adjust_for_matrix_type(SkSLType type, int count) {
// All Layouts flatten matrices and arrays of matrices into arrays of columns, so update
// 'type' to be the column type and either multiply 'count' by the number of columns for
// arrays of matrices, or set to exactly the number of columns for a "non-array" matrix.
switch(type) {
case SkSLType::kFloat2x2: return {SkSLType::kFloat2, 2*std::max(1, count)};
case SkSLType::kFloat3x3: return {SkSLType::kFloat3, 3*std::max(1, count)};
case SkSLType::kFloat4x4: return {SkSLType::kFloat4, 4*std::max(1, count)};
case SkSLType::kHalf2x2: return {SkSLType::kHalf2, 2*std::max(1, count)};
case SkSLType::kHalf3x3: return {SkSLType::kHalf3, 3*std::max(1, count)};
case SkSLType::kHalf4x4: return {SkSLType::kHalf4, 4*std::max(1, count)};
// Otherwise leave type and count alone.
default: return {type, count};
}
}
void UniformManager::write(const Uniform& u, const void* data) {
SkASSERT(SkSLTypeCanBeUniformValue(u.type()));
SkASSERT(!u.isPaintColor()); // Must go through writePaintColor()
auto [type, count] = adjust_for_matrix_type(u.type(), u.count());
SkASSERT(SkSLTypeMatrixSize(type) < 0); // Matrix types should have been flattened
const bool fullPrecision = LayoutRules::UseFullPrecision(fLayout) || !IsHalfVector(type);
if (count == Uniform::kNonArray) {
if (fullPrecision) {
switch(SkSLTypeVecLength(type)) {
case 1: this->write<1, /*Half=*/false>(data, type); break;
case 2: this->write<2, /*Half=*/false>(data, type); break;
case 3: this->write<3, /*Half=*/false>(data, type); break;
case 4: this->write<4, /*Half=*/false>(data, type); break;
}
} else {
switch(SkSLTypeVecLength(type)) {
case 1: this->write<1, /*Half=*/true>(data, type); break;
case 2: this->write<2, /*Half=*/true>(data, type); break;
case 3: this->write<3, /*Half=*/true>(data, type); break;
case 4: this->write<4, /*Half=*/true>(data, type); break;
}
}
} else {
if (fullPrecision) {
switch(SkSLTypeVecLength(type)) {
case 1: this->writeArray<1, /*Half=*/false>(data, count, type); break;
case 2: this->writeArray<2, /*Half=*/false>(data, count, type); break;
case 3: this->writeArray<3, /*Half=*/false>(data, count, type); break;
case 4: this->writeArray<4, /*Half=*/false>(data, count, type); break;
}
} else {
switch(SkSLTypeVecLength(type)) {
case 1: this->writeArray<1, /*Half=*/true>(data, count, type); break;
case 2: this->writeArray<2, /*Half=*/true>(data, count, type); break;
case 3: this->writeArray<3, /*Half=*/true>(data, count, type); break;
case 4: this->writeArray<4, /*Half=*/true>(data, count, type); break;
}
}
}
}
#if defined(SK_DEBUG)
void UniformManager::checkBeginStruct(int baseAlignment) {
// Wrote a struct field before the struct was started
SkASSERT(fExpectedUniformIndex == 0);
// Not expecting to start a struct (layout must be valid)
SkASSERT(fSubstructCalculator.layout() != Layout::kInvalid);
// Somehow already started a substruct (base alignment should be <= 0 initially)
SkASSERT(fStructBaseAlignment <= 0);
// Empty substructs are not allowed
SkASSERT(!fExpectedUniforms.empty());
// Assume the expected uniforms describe the whole substruct
auto structCalculator = UniformOffsetCalculator::ForStruct(fLayout);
for (const Uniform& f : fExpectedUniforms) {
structCalculator.advanceOffset(f.type(), f.count());
}
// Calculated alignment must match the passed base alignment
SkASSERT(baseAlignment == structCalculator.requiredAlignment());
fSubstructStartingOffset = fOffsetCalculator.advanceStruct(structCalculator);
}
void UniformManager::checkEndStruct() {
// Didn't write all the expected fields before ending the struct
SkASSERT(fExpectedUniformIndex == (int)fExpectedUniforms.size());
// Not expecting a struct (layout must be valid)
SkASSERT(fSubstructCalculator.layout() != Layout::kInvalid);
// Missing a beginStruct() (base alignment must be > 0 if we are in a struct)
SkASSERT(fStructBaseAlignment > 0);
// `fStructCalculator` should now have been advanced equivalently to the substruct calculator
// used in checkBeginStruct() to calculate the expected starting offset.
const int structSize = SkAlignTo(fSubstructCalculator.size(),
fSubstructCalculator.requiredAlignment());
// Somehow didn't end on the correct boundary
SkASSERT(fStorage.size() == fSubstructStartingOffset + structSize);
// UniformManager's alignment got out of sync with expected alignment
SkASSERT(fReqAlignment == fOffsetCalculator.requiredAlignment());
SkASSERT(fReqAlignment >= fSubstructCalculator.requiredAlignment());
// Reset the substruct calculator to mark that the struct has been completed
fSubstructCalculator = {};
}
void UniformManager::checkExpected(const void* dst, SkSLType type, int count) {
// A write() outside of a UniformExpectationsVisitor or too many uniforms written for what
// is expected.
SkASSERT(fExpectedUniformIndex < SkTo<int>(fExpectedUniforms.size()));
if (fSubstructCalculator.layout() != Layout::kInvalid) {
// A write() that should be inside a struct, but missing a call to beginStruct()
SkASSERT(fStructBaseAlignment > 0);
} else {
// A substruct was started when it shouldn't have been.
SkASSERT(fStructBaseAlignment <= 0);
}
const Uniform& expected = fExpectedUniforms[fExpectedUniformIndex++];
// Not all types are supported as uniforms or supported by UniformManager
SkASSERT(SkSLTypeCanBeUniformValue(expected.type()));
auto [expectedType, expectedCount] = adjust_for_matrix_type(expected.type(), expected.count());
SkASSERT(expectedType == type && expectedCount == count);
if (dst) {
// If we have 'dst', it's the aligned starting offset of the uniform being checked, so
// subtracting the address of the first byte in fStorage gives us the offset.
int offset = static_cast<int>(reinterpret_cast<intptr_t>(dst) -
reinterpret_cast<intptr_t>(fStorage.data()) -
fNonShadingOffset);
if (fSubstructCalculator.layout() == Layout::kInvalid) {
// Pass original expected type and count to the offset calculator for validation.
SkASSERT(offset == fOffsetCalculator.advanceOffset(expected.type(), expected.count()));
SkASSERT(fReqAlignment == fOffsetCalculator.requiredAlignment());
// And if it is the paint color uniform, we should not have already written it.
SkASSERT(!(fWrotePaintColor && expected.isPaintColor()));
} else {
int relOffset = fSubstructCalculator.advanceOffset(expected.type(), expected.count());
SkASSERT(offset == fSubstructStartingOffset + relOffset);
// The overall required alignment might already be higher from prior fields, but should
// be at least what's required by the substruct.
SkASSERT(fReqAlignment >= fSubstructCalculator.requiredAlignment());
// And it should not be a paint color uniform within a substruct.
SkASSERT(!expected.isPaintColor());
}
} else {
// If 'dst' is null, it's an already-visited paint color uniform, so it's not being written
// and not changing the offset, and should not be part of a substruct.
SkASSERT(fWrotePaintColor);
SkASSERT(fSubstructCalculator.layout() == Layout::kInvalid);
SkASSERT(expected.isPaintColor());
}
}
bool UniformManager::isReset() const {
return fStorage.empty();
}
void UniformManager::setExpectedUniforms(SkSpan<const Uniform> expected, bool isSubstruct) {
fExpectedUniforms = expected;
fExpectedUniformIndex = 0;
if (isSubstruct) {
// Start collecting the subsequent uniforms with a 0-based offset to determine their
// relative layout and required base alignment of the entire struct.
fSubstructCalculator = UniformOffsetCalculator::ForStruct(fLayout);
} else {
// Expected uniforms will advance fOffsetCalculator directly
SkASSERT(fSubstructCalculator.layout() == Layout::kInvalid);
}
}
void UniformManager::doneWithExpectedUniforms() {
SkASSERT(fExpectedUniformIndex == static_cast<int>(fExpectedUniforms.size()));
// Any expected substruct should have been ended and validated inside endStruct(); if this fails
// it means there is a missing endStruct().
SkASSERT(fSubstructCalculator.layout() == Layout::kInvalid);
fExpectedUniforms = {};
}
#endif // SK_DEBUG
} // namespace skgpu::graphite