mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 14:18:31 +09:00
[Feat] (DirectVulkan): GPU transform feedback capture via VK_EXT_transform_feedback
Second stage of GL 3.0 transform feedback: captured draws now write real data. - Device setup enables the VK_EXT_transform_feedback feature when present and loads the bind/begin/end entry points. - Captured draws compile an XfbCapture program variant whose last vertex-processing stage gets XfbBuffer/XfbStride/Offset decorations from the program's resolved varyings (a new spirv-opt pass). A captured gl_Position is mirrored into a dedicated output written before every OpReturn - or before every OpEmitVertex in a geometry stage - ahead of the position fixup, so the captured value is the shader's own pre-remap position. - DrawArrays/DrawElements wrap the draw in Begin/EndTransformFeedbackEXT; a small counter buffer resumes the append position across draws within one glBeginTransformFeedback (fresh Begin starts at the bound offsets). - Capture targets are promoted to persistently-mapped host-coherent GPU storage (persistent-map storage now also carries the transform feedback usage), so MapBuffer/GetBufferSubData read the captured bytes after the fence wait glEndTransformFeedback now performs. - Draw-mode/feedback-mode validation defers to the geometry shader's output primitive when one is present, and glGetBooleanv reports GL_TRANSFORM_FEEDBACK_ACTIVE/PAUSED so dEQP's per-case state reset can unwind an active capture. KHR-GL33: transform_feedback capture_vertex_*/capture_geometry_*/ discard_*/draw_xfb and clip_distance.coverage now pass; queries (PRIMITIVES_WRITTEN) and gl_ClipDistance capture remain.
This commit is contained in:
@@ -923,6 +923,163 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
ProgramFactory::CompileOptionFlags m_transformFlags;
|
ProgramFactory::CompileOptionFlags m_transformFlags;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Decorates the module's captured varyings for VK_EXT_transform_feedback:
|
||||||
|
// user outputs get XfbBuffer/XfbStride/Offset directly; a captured
|
||||||
|
// gl_Position (a gl_PerVertex member) is mirrored into a dedicated output
|
||||||
|
// variable copied before every OpReturn, BEFORE the position fixup runs,
|
||||||
|
// so the captured value is the shader's own (pre-remap) gl_Position.
|
||||||
|
class XfbCaptureDecoratePass final : public spvtools::opt::Pass {
|
||||||
|
public:
|
||||||
|
struct CapturedVarying {
|
||||||
|
std::string name;
|
||||||
|
Uint32 bufferIndex = 0;
|
||||||
|
Uint32 offsetBytes = 0;
|
||||||
|
};
|
||||||
|
const char* name() const override { return "mobilegl-xfb-capture-decorate"; }
|
||||||
|
XfbCaptureDecoratePass(Vector<CapturedVarying> varyings, Vector<Uint32> strides)
|
||||||
|
: m_varyings(Move(varyings)), m_strides(Move(strides)) {}
|
||||||
|
|
||||||
|
Status Process() override {
|
||||||
|
using namespace spvtools::opt;
|
||||||
|
if (m_varyings.empty()) return Status::SuccessWithoutChange;
|
||||||
|
|
||||||
|
auto entryPointIter = get_module()->entry_points().begin();
|
||||||
|
if (entryPointIter == get_module()->entry_points().end()) return Status::SuccessWithoutChange;
|
||||||
|
spvtools::opt::Instruction* entryPoint = &*entryPointIter;
|
||||||
|
const Uint32 entryFunctionId = entryPoint->GetSingleWordInOperand(1);
|
||||||
|
|
||||||
|
// Name -> result id map from the debug section.
|
||||||
|
std::unordered_map<std::string, Uint32> idsByName;
|
||||||
|
for (auto& debugInst : get_module()->debugs2()) {
|
||||||
|
if (debugInst.opcode() != spv::Op::OpName) continue;
|
||||||
|
idsByName[debugInst.GetInOperand(1).AsString()] = debugInst.GetSingleWordInOperand(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* decorationManager = context()->get_decoration_mgr();
|
||||||
|
const auto decorateForXfb = [&](Uint32 targetId, Uint32 bufferIndex, Uint32 offsetBytes) {
|
||||||
|
const Uint32 stride = bufferIndex < m_strides.size() ? m_strides[bufferIndex] : 0;
|
||||||
|
decorationManager->AddDecorationVal(targetId, static_cast<Uint32>(spv::Decoration::XfbBuffer),
|
||||||
|
bufferIndex);
|
||||||
|
decorationManager->AddDecorationVal(targetId, static_cast<Uint32>(spv::Decoration::XfbStride),
|
||||||
|
stride);
|
||||||
|
decorationManager->AddDecorationVal(targetId, static_cast<Uint32>(spv::Decoration::Offset),
|
||||||
|
offsetBytes);
|
||||||
|
};
|
||||||
|
|
||||||
|
Bool modified = false;
|
||||||
|
Bool needsPositionMirror = false;
|
||||||
|
Uint32 positionBufferIndex = 0;
|
||||||
|
Uint32 positionOffset = 0;
|
||||||
|
for (const auto& varying : m_varyings) {
|
||||||
|
if (varying.name == "gl_Position") {
|
||||||
|
needsPositionMirror = true;
|
||||||
|
positionBufferIndex = varying.bufferIndex;
|
||||||
|
positionOffset = varying.offsetBytes;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto idIt = idsByName.find(varying.name);
|
||||||
|
if (idIt == idsByName.end()) {
|
||||||
|
MGLOG_E("XfbCaptureDecoratePass: no SPIR-V variable named '%s'", varying.name.c_str());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
decorateForXfb(idIt->second, varying.bufferIndex, varying.offsetBytes);
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsPositionMirror) {
|
||||||
|
modified |= MirrorPositionForCapture(entryFunctionId, *entryPoint, positionBufferIndex,
|
||||||
|
positionOffset, decorateForXfb);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!modified) return Status::SuccessWithoutChange;
|
||||||
|
|
||||||
|
context()->AddCapability(spv::Capability::TransformFeedback);
|
||||||
|
{
|
||||||
|
auto executionMode = MakeUnique<spvtools::opt::Instruction>(
|
||||||
|
context(), spv::Op::OpExecutionMode, 0, 0,
|
||||||
|
std::initializer_list<spvtools::opt::Operand>{
|
||||||
|
{SPV_OPERAND_TYPE_ID, {entryPoint->GetSingleWordInOperand(1)}},
|
||||||
|
{SPV_OPERAND_TYPE_EXECUTION_MODE, {static_cast<Uint32>(spv::ExecutionMode::Xfb)}}});
|
||||||
|
get_module()->AddExecutionMode(Move(executionMode));
|
||||||
|
}
|
||||||
|
context()->InvalidateAnalysesExceptFor(spvtools::opt::IRContext::kAnalysisNone);
|
||||||
|
return Status::SuccessWithChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <typename DecorateFn>
|
||||||
|
Bool MirrorPositionForCapture(Uint32 entryFunctionId, spvtools::opt::Instruction& entryPoint,
|
||||||
|
Uint32 bufferIndex, Uint32 offsetBytes, const DecorateFn& decorateForXfb) {
|
||||||
|
const Uint32 entryPointModel = entryPoint.GetSingleWordInOperand(0);
|
||||||
|
using namespace spvtools::opt;
|
||||||
|
PositionTargetInfo target{};
|
||||||
|
if (!FindPositionTarget(context(), &target)) {
|
||||||
|
MGLOG_E("XfbCaptureDecoratePass: gl_Position capture requested but no position output found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!target.isMember) {
|
||||||
|
// Standalone gl_Position variable: decorate it directly.
|
||||||
|
decorateForXfb(target.variableId, bufferIndex, offsetBytes);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* typeManager = context()->get_type_mgr();
|
||||||
|
const Uint32 mirrorPointerTypeId =
|
||||||
|
typeManager->FindPointerToType(target.vectorTypeId, spv::StorageClass::Output);
|
||||||
|
if (mirrorPointerTypeId == 0) return false;
|
||||||
|
|
||||||
|
const Uint32 mirrorVariableId = context()->TakeNextId();
|
||||||
|
auto mirrorVariable = MakeUnique<Instruction>(
|
||||||
|
context(), spv::Op::OpVariable, mirrorPointerTypeId, mirrorVariableId,
|
||||||
|
std::initializer_list<Operand>{
|
||||||
|
{SPV_OPERAND_TYPE_STORAGE_CLASS, {static_cast<Uint32>(spv::StorageClass::Output)}}});
|
||||||
|
get_module()->AddGlobalValue(Move(mirrorVariable));
|
||||||
|
|
||||||
|
// A free output location: past every explicitly decorated output.
|
||||||
|
Uint32 mirrorLocation = 0;
|
||||||
|
for (auto& annotation : get_module()->annotations()) {
|
||||||
|
if (annotation.opcode() != spv::Op::OpDecorate ||
|
||||||
|
annotation.GetSingleWordInOperand(1) != static_cast<Uint32>(spv::Decoration::Location)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
mirrorLocation = std::max(mirrorLocation, annotation.GetSingleWordInOperand(2) + 1);
|
||||||
|
}
|
||||||
|
auto* decorationManager = context()->get_decoration_mgr();
|
||||||
|
decorationManager->AddDecorationVal(mirrorVariableId,
|
||||||
|
static_cast<Uint32>(spv::Decoration::Location), mirrorLocation);
|
||||||
|
decorateForXfb(mirrorVariableId, bufferIndex, offsetBytes);
|
||||||
|
entryPoint.AddOperand({SPV_OPERAND_TYPE_ID, {mirrorVariableId}});
|
||||||
|
|
||||||
|
auto* function = context()->GetFunction(entryFunctionId);
|
||||||
|
if (function == nullptr) return false;
|
||||||
|
const auto model = static_cast<spv::ExecutionModel>(entryPointModel);
|
||||||
|
Bool injected = false;
|
||||||
|
for (auto& block : *function) {
|
||||||
|
for (auto instIter = block.begin(); instIter != block.end(); ++instIter) {
|
||||||
|
// Geometry stages capture per emitted vertex; other stages at return.
|
||||||
|
const Bool isInjectionSite =
|
||||||
|
model == spv::ExecutionModel::Geometry
|
||||||
|
? instIter->opcode() == spv::Op::OpEmitVertex
|
||||||
|
: instIter->opcode() == spv::Op::OpReturn;
|
||||||
|
if (!isInjectionSite) continue;
|
||||||
|
InstructionBuilder builder(context(), &*instIter, IRContext::kAnalysisNone);
|
||||||
|
const Uint32 memberIndexId = builder.GetUintConstantId(target.memberIndex);
|
||||||
|
auto* access =
|
||||||
|
builder.AddAccessChain(target.vectorPtrTypeId, target.variableId, {memberIndexId});
|
||||||
|
if (access == nullptr) return injected;
|
||||||
|
auto* value = builder.AddLoad(target.vectorTypeId, access->result_id());
|
||||||
|
if (value == nullptr) return injected;
|
||||||
|
builder.AddStore(mirrorVariableId, value->result_id());
|
||||||
|
injected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return injected;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<CapturedVarying> m_varyings;
|
||||||
|
Vector<Uint32> m_strides;
|
||||||
|
};
|
||||||
|
|
||||||
// Adreno 650 (driver 512.502) faults the GPU on an implicit-LOD sample of a full-screen
|
// Adreno 650 (driver 512.502) faults the GPU on an implicit-LOD sample of a full-screen
|
||||||
// colour render target: the texture unit's derivative path reads outside the image's
|
// colour render target: the texture unit's derivative path reads outside the image's
|
||||||
// allocation even though the sampler clamps LOD to 0 and the mapping is 1:1. MobileGL's
|
// allocation even though the sampler clamps LOD to 0 and the mapping is 1:1. MobileGL's
|
||||||
@@ -1128,6 +1285,41 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return spvtools::Optimizer::PassToken(MakeUnique<GlToVulkanPositionFixPass>(transformFlags));
|
return spvtools::Optimizer::PassToken(MakeUnique<GlToVulkanPositionFixPass>(transformFlags));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bool TransformSpirvForXfbCapture(const Vector<Uint>& input, Vector<Uint>& output,
|
||||||
|
const MG_State::GLState::ProgramObject& program) {
|
||||||
|
if (input.empty()) {
|
||||||
|
output.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Vector<XfbCaptureDecoratePass::CapturedVarying> varyings;
|
||||||
|
varyings.reserve(program.GetTransformFeedbackVaryingCount());
|
||||||
|
for (const auto& varying : program.GetTransformFeedbackVaryings()) {
|
||||||
|
varyings.push_back({varying.name, varying.bufferIndex, varying.offsetBytes});
|
||||||
|
}
|
||||||
|
Vector<Uint32> strides;
|
||||||
|
strides.reserve(program.GetTransformFeedbackBufferCount());
|
||||||
|
for (SizeT i = 0; i < program.GetTransformFeedbackBufferCount(); ++i) {
|
||||||
|
strides.push_back(program.GetTransformFeedbackStride(static_cast<Uint32>(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
spvtools::Optimizer optimizer(SPV_ENV_VULKAN_1_3);
|
||||||
|
spvtools::OptimizerOptions options;
|
||||||
|
options.set_run_validator(false);
|
||||||
|
optimizer.SetMessageConsumer([](spv_message_level_t, const char*, const spv_position_t&,
|
||||||
|
const char* message) {
|
||||||
|
MGLOG_E("Vulkan: xfb capture pass: %s", message != nullptr ? message : "");
|
||||||
|
});
|
||||||
|
optimizer.RegisterPass(spvtools::Optimizer::PassToken(
|
||||||
|
MakeUnique<XfbCaptureDecoratePass>(Move(varyings), Move(strides))));
|
||||||
|
|
||||||
|
const Bool success = optimizer.Run(input.data(), input.size(), &output, options);
|
||||||
|
if (!success) {
|
||||||
|
MGLOG_E("Vulkan: xfb capture decoration pass failed; keeping the original module");
|
||||||
|
output = input;
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
Bool TransformSpirvForVulkanPositionFix(const Vector<Uint>& input, Vector<Uint>& output,
|
Bool TransformSpirvForVulkanPositionFix(const Vector<Uint>& input, Vector<Uint>& output,
|
||||||
ProgramFactory::CompileOptionFlags transformFlags) {
|
ProgramFactory::CompileOptionFlags transformFlags) {
|
||||||
if (input.empty()) {
|
if (input.empty()) {
|
||||||
@@ -2173,7 +2365,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
// Apply position fixup if needed
|
// Apply position fixup if needed
|
||||||
if (fixupStage != ShaderStage::Unknown && shaders[i] && shaders[i]->GetShaderStage() == fixupStage) {
|
if (fixupStage != ShaderStage::Unknown && shaders[i] && shaders[i]->GetShaderStage() == fixupStage) {
|
||||||
TransformSpirvForVulkanPositionFix(spv, moduleSpirvs[i], flags);
|
const Vector<Uint>* fixupInput = &spv;
|
||||||
|
Vector<Uint> xfbSpirv;
|
||||||
|
if ((flags & ProgramFactory::CompileOptionBit::XfbCapture) &&
|
||||||
|
program.GetTransformFeedbackVaryingCount() > 0) {
|
||||||
|
// Decorate BEFORE the position fixup so a captured gl_Position
|
||||||
|
// mirror copies the shader's own (pre-remap) value.
|
||||||
|
if (TransformSpirvForXfbCapture(spv, xfbSpirv, program)) {
|
||||||
|
fixupInput = &xfbSpirv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TransformSpirvForVulkanPositionFix(*fixupInput, moduleSpirvs[i], flags);
|
||||||
} else {
|
} else {
|
||||||
moduleSpirvs[i] = spv;
|
moduleSpirvs[i] = spv;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// level, which makes the two forms produce identical texels (the implicit lambda is
|
// level, which makes the two forms produce identical texels (the implicit lambda is
|
||||||
// clamped into [minLod, maxLod] = [0, 0] regardless of derivatives or bias).
|
// clamped into [minLod, maxLod] = [0, 0] regardless of derivatives or bias).
|
||||||
ExplicitLod0Sampling = 1 << 5,
|
ExplicitLod0Sampling = 1 << 5,
|
||||||
|
// Decorates the last vertex-processing stage's captured varyings with
|
||||||
|
// XfbBuffer/XfbStride/Offset (VK_EXT_transform_feedback). Set only for draws
|
||||||
|
// recorded while GL transform feedback is active, so plain draws keep the
|
||||||
|
// undecorated variant.
|
||||||
|
XfbCapture = 1 << 6,
|
||||||
};
|
};
|
||||||
using CompileOptionFlags = Flags<CompileOptionBit>;
|
using CompileOptionFlags = Flags<CompileOptionBit>;
|
||||||
using HashType = Uint64;
|
using HashType = Uint64;
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
|
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
|
||||||
VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT |
|
VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT |
|
||||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||||
|
// Appended to kPersistentBackedUsage when VK_EXT_transform_feedback is enabled
|
||||||
|
// (see VkBufferManagerInitInfo::transformFeedbackUsageEnabled).
|
||||||
|
constexpr VkBufferUsageFlags kTransformFeedbackUsage =
|
||||||
|
VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT;
|
||||||
// The app writes into the persistent map with no explicit flush, so its memory must
|
// The app writes into the persistent map with no explicit flush, so its memory must
|
||||||
// be host-coherent (Adreno host-visible memory is; requiring it keeps us portable).
|
// be host-coherent (Adreno host-visible memory is; requiring it keeps us portable).
|
||||||
constexpr VkMemoryPropertyFlags kPersistentBackedRequiredFlags =
|
constexpr VkMemoryPropertyFlags kPersistentBackedRequiredFlags =
|
||||||
@@ -465,7 +469,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// it from the current shadow - MappedData() is still the shadow here because the
|
// it from the current shadow - MappedData() is still the shadow here because the
|
||||||
// frontend adopts (and drops) the shadow only after this returns.
|
// frontend adopts (and drops) the shadow only after this returns.
|
||||||
DeferRelease(std::move(resource->buffer));
|
DeferRelease(std::move(resource->buffer));
|
||||||
if (!CreateResidentStorage(*resource, size, kPersistentBackedUsage, kPersistentBackedRequiredFlags)) {
|
const VkBufferUsageFlags persistentUsage =
|
||||||
|
kPersistentBackedUsage |
|
||||||
|
(m_initInfo.transformFeedbackUsageEnabled ? kTransformFeedbackUsage : 0);
|
||||||
|
if (!CreateResidentStorage(*resource, size, persistentUsage, kPersistentBackedRequiredFlags)) {
|
||||||
resource->persistentMapped = false;
|
resource->persistentMapped = false;
|
||||||
resource->storageSize = 0;
|
resource->storageSize = 0;
|
||||||
resource->usageFlags = 0;
|
resource->usageFlags = 0;
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VmaMemoryUsage transientMemoryUsage = VMA_MEMORY_USAGE_AUTO;
|
VmaMemoryUsage transientMemoryUsage = VMA_MEMORY_USAGE_AUTO;
|
||||||
VmaAllocationCreateFlags transientAllocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
VmaAllocationCreateFlags transientAllocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
||||||
Bool transientPersistentMapping = false;
|
Bool transientPersistentMapping = false;
|
||||||
|
// VK_EXT_transform_feedback is enabled: persistent-map storage additionally
|
||||||
|
// carries the transform feedback usage so capture targets can bind directly.
|
||||||
|
Bool transformFeedbackUsageEnabled = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// The DirectVulkan storage behind one frontend buffer (pipe_resource analogue).
|
// The DirectVulkan storage behind one frontend buffer (pipe_resource analogue).
|
||||||
|
|||||||
@@ -2581,6 +2581,7 @@ void main() {
|
|||||||
.transientMemoryUsage = VMA_MEMORY_USAGE_AUTO,
|
.transientMemoryUsage = VMA_MEMORY_USAGE_AUTO,
|
||||||
.transientAllocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT,
|
.transientAllocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT,
|
||||||
.transientPersistentMapping = true,
|
.transientPersistentMapping = true,
|
||||||
|
.transformFeedbackUsageEnabled = m_transformFeedbackFeatureEnabled,
|
||||||
});
|
});
|
||||||
MOBILEGL_ASSERT(succeeded, "VkBufferManager initialization failed.");
|
MOBILEGL_ASSERT(succeeded, "VkBufferManager initialization failed.");
|
||||||
m_bufferManager.SetCopyCommandProvider(this);
|
m_bufferManager.SetCopyCommandProvider(this);
|
||||||
@@ -2745,6 +2746,7 @@ void main() {
|
|||||||
m_textureManager.reset();
|
m_textureManager.reset();
|
||||||
}
|
}
|
||||||
m_vertexInputStateFactory.reset();
|
m_vertexInputStateFactory.reset();
|
||||||
|
m_xfbCounterBuffer.Destroy();
|
||||||
m_bufferManager.Shutdown();
|
m_bufferManager.Shutdown();
|
||||||
|
|
||||||
// Device is idle (vkDeviceWaitIdle above); query pools can be destroyed.
|
// Device is idle (vkDeviceWaitIdle above); query pools can be destroyed.
|
||||||
@@ -4618,6 +4620,11 @@ void main() {
|
|||||||
const auto& vao = *MG_State::pGLContext->GetBoundVertexArray();
|
const auto& vao = *MG_State::pGLContext->GetBoundVertexArray();
|
||||||
const auto& program = *MG_State::pGLContext->GetCurrentProgram();
|
const auto& program = *MG_State::pGLContext->GetCurrentProgram();
|
||||||
ProgramFactory::CompileOptionFlags transformFlags = GetShaderTransformFlags(m_swapchainObject.GetPreTransform());
|
ProgramFactory::CompileOptionFlags transformFlags = GetShaderTransformFlags(m_swapchainObject.GetPreTransform());
|
||||||
|
// Captured draws take the xfb-decorated program variant.
|
||||||
|
if (m_transformFeedbackFeatureEnabled && MG_State::pGLContext->IsTransformFeedbackActive() &&
|
||||||
|
program.GetTransformFeedbackVaryingCount() > 0) {
|
||||||
|
transformFlags |= ProgramFactory::CompileOptionBit::XfbCapture;
|
||||||
|
}
|
||||||
// Sampling a colour render target through the driver's implicit-LOD path faults the GPU on
|
// Sampling a colour render target through the driver's implicit-LOD path faults the GPU on
|
||||||
// Adreno 650 (see ForceExplicitLod0SamplePass); ask for the explicit-LOD variant when doing
|
// Adreno 650 (see ForceExplicitLod0SamplePass); ask for the explicit-LOD variant when doing
|
||||||
// so cannot change a texel, i.e. when every sampler this program reads is pinned to a
|
// so cannot change a texel, i.e. when every sampler this program reads is pinned to a
|
||||||
@@ -7292,6 +7299,100 @@ void main() {
|
|||||||
resource->layout = finalLayout;
|
resource->layout = finalLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bool VulkanRenderer::BeginXfbCaptureForDraw(FrameContext::FrameData& frame) {
|
||||||
|
if (!m_transformFeedbackFeatureEnabled || MG_State::pGLContext == nullptr ||
|
||||||
|
!MG_State::pGLContext->IsTransformFeedbackActive()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto& program = MG_State::pGLContext->GetTransformFeedbackProgram();
|
||||||
|
if (!program || program->GetTransformFeedbackVaryingCount() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const SizeT bufferCount = std::min<SizeT>(program->GetTransformFeedbackBufferCount(), 4);
|
||||||
|
if (bufferCount == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_xfbCounterBuffer.IsValid()) {
|
||||||
|
if (!m_xfbCounterBuffer.Create({
|
||||||
|
.allocator = m_allocator,
|
||||||
|
.size = 16,
|
||||||
|
.usage = VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT |
|
||||||
|
VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||||
|
.memoryUsage = VMA_MEMORY_USAGE_AUTO,
|
||||||
|
})) {
|
||||||
|
MGLOG_E("BeginXfbCaptureForDraw: failed to create the counter buffer");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VkBuffer buffers[4] = {};
|
||||||
|
VkDeviceSize offsets[4] = {};
|
||||||
|
VkDeviceSize sizes[4] = {};
|
||||||
|
for (SizeT i = 0; i < bufferCount; ++i) {
|
||||||
|
auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback,
|
||||||
|
static_cast<Uint>(i));
|
||||||
|
const auto& bufferObject = point.GetBoundObject();
|
||||||
|
if (bufferObject == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Host-visible coherent GPU residency: the capture writes land where
|
||||||
|
// MapBuffer/GetBufferSubData read.
|
||||||
|
bufferObject->EnsureGpuResidentStorage();
|
||||||
|
BufferSlice slice{};
|
||||||
|
if (!m_bufferManager.AcquireResidentSlice(BufferKind::Vertex, bufferObject, slice)) {
|
||||||
|
MGLOG_E("BeginXfbCaptureForDraw: failed to acquire capture buffer %zu", i);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const Range1D range = point.GetRange();
|
||||||
|
const VkDeviceSize rangeStart = static_cast<VkDeviceSize>(range.start);
|
||||||
|
const VkDeviceSize rangeSize = range.end > range.start
|
||||||
|
? static_cast<VkDeviceSize>(range.end - range.start)
|
||||||
|
: VK_WHOLE_SIZE;
|
||||||
|
buffers[i] = slice.buffer;
|
||||||
|
offsets[i] = slice.offset + rangeStart;
|
||||||
|
sizes[i] = rangeSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_vkCmdBindTransformFeedbackBuffersEXT(frame.commandBuffer, 0, static_cast<Uint32>(bufferCount), buffers,
|
||||||
|
offsets, sizes);
|
||||||
|
|
||||||
|
const Uint64 generation = MG_State::pGLContext->GetTransformFeedbackGeneration();
|
||||||
|
const Bool resume = m_xfbCountersValid && m_xfbLastSeenGeneration == generation;
|
||||||
|
m_xfbLastSeenGeneration = generation;
|
||||||
|
|
||||||
|
VkBuffer counterBuffers[4] = {};
|
||||||
|
VkDeviceSize counterOffsets[4] = {};
|
||||||
|
for (SizeT i = 0; i < bufferCount; ++i) {
|
||||||
|
counterBuffers[i] = m_xfbCounterBuffer.GetHandle();
|
||||||
|
counterOffsets[i] = static_cast<VkDeviceSize>(i) * 4;
|
||||||
|
}
|
||||||
|
if (resume) {
|
||||||
|
s_vkCmdBeginTransformFeedbackEXT(frame.commandBuffer, 0, static_cast<Uint32>(bufferCount),
|
||||||
|
counterBuffers, counterOffsets);
|
||||||
|
} else {
|
||||||
|
s_vkCmdBeginTransformFeedbackEXT(frame.commandBuffer, 0, 0, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::EndXfbCaptureForDraw(FrameContext::FrameData& frame, Bool began) {
|
||||||
|
if (!began) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto& program = MG_State::pGLContext->GetTransformFeedbackProgram();
|
||||||
|
const SizeT bufferCount = program ? std::min<SizeT>(program->GetTransformFeedbackBufferCount(), 4) : 0;
|
||||||
|
VkBuffer counterBuffers[4] = {};
|
||||||
|
VkDeviceSize counterOffsets[4] = {};
|
||||||
|
for (SizeT i = 0; i < bufferCount; ++i) {
|
||||||
|
counterBuffers[i] = m_xfbCounterBuffer.GetHandle();
|
||||||
|
counterOffsets[i] = static_cast<VkDeviceSize>(i) * 4;
|
||||||
|
}
|
||||||
|
s_vkCmdEndTransformFeedbackEXT(frame.commandBuffer, 0, static_cast<Uint32>(bufferCount), counterBuffers,
|
||||||
|
counterOffsets);
|
||||||
|
m_xfbCountersValid = true;
|
||||||
|
}
|
||||||
|
|
||||||
void VulkanRenderer::DrawArrays(const DrawCmd& payload) {
|
void VulkanRenderer::DrawArrays(const DrawCmd& payload) {
|
||||||
auto& frame = m_frameContext.GetCurrent();
|
auto& frame = m_frameContext.GetCurrent();
|
||||||
|
|
||||||
@@ -7303,11 +7404,13 @@ void main() {
|
|||||||
|
|
||||||
VkCommandBuffer& commandBuffer = frame.commandBuffer;
|
VkCommandBuffer& commandBuffer = frame.commandBuffer;
|
||||||
|
|
||||||
|
const Bool xfbActive = BeginXfbCaptureForDraw(frame);
|
||||||
vkCmdDraw(commandBuffer,
|
vkCmdDraw(commandBuffer,
|
||||||
payload.params.vertexCount,
|
payload.params.vertexCount,
|
||||||
payload.params.instanceCount,
|
payload.params.instanceCount,
|
||||||
payload.params.firstVertex,
|
payload.params.firstVertex,
|
||||||
payload.params.firstInstance);
|
payload.params.firstInstance);
|
||||||
|
EndXfbCaptureForDraw(frame, xfbActive);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanRenderer::DrawElements(const DrawIndexedCmd& payload) {
|
void VulkanRenderer::DrawElements(const DrawIndexedCmd& payload) {
|
||||||
@@ -7334,12 +7437,14 @@ void main() {
|
|||||||
|
|
||||||
VkCommandBuffer& commandBuffer = frame.commandBuffer;
|
VkCommandBuffer& commandBuffer = frame.commandBuffer;
|
||||||
|
|
||||||
|
const Bool xfbActive = BeginXfbCaptureForDraw(frame);
|
||||||
vkCmdDrawIndexed(commandBuffer,
|
vkCmdDrawIndexed(commandBuffer,
|
||||||
payload.params.indexCount,
|
payload.params.indexCount,
|
||||||
payload.params.instanceCount,
|
payload.params.instanceCount,
|
||||||
payload.params.firstIndex,
|
payload.params.firstIndex,
|
||||||
payload.params.vertexOffset,
|
payload.params.vertexOffset,
|
||||||
payload.params.firstInstance);
|
payload.params.firstInstance);
|
||||||
|
EndXfbCaptureForDraw(frame, xfbActive);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanRenderer::MultiDrawArrays(const MultiDrawCmd& payload) {
|
void VulkanRenderer::MultiDrawArrays(const MultiDrawCmd& payload) {
|
||||||
@@ -8817,6 +8922,31 @@ void main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VK_EXT_transform_feedback backs GL transform feedback capture.
|
||||||
|
m_transformFeedbackFeatureEnabled = false;
|
||||||
|
VkPhysicalDeviceTransformFeedbackFeaturesEXT transformFeedbackFeatures{};
|
||||||
|
transformFeedbackFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT;
|
||||||
|
if (IsExtensionSupported(availableExtensions, VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME) &&
|
||||||
|
getPhysicalDeviceFeatures2 != nullptr) {
|
||||||
|
VkPhysicalDeviceFeatures2 featureQuery{};
|
||||||
|
featureQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
||||||
|
featureQuery.pNext = &transformFeedbackFeatures;
|
||||||
|
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
|
||||||
|
if (transformFeedbackFeatures.transformFeedback == VK_TRUE) {
|
||||||
|
if (!IsExtensionAlreadyEnabled(enabledDeviceExtensions, VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME)) {
|
||||||
|
enabledDeviceExtensions.push_back(VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME);
|
||||||
|
}
|
||||||
|
transformFeedbackFeatures.geometryStreams = VK_FALSE;
|
||||||
|
transformFeedbackFeatures.pNext = const_cast<void*>(deviceCreateInfo.pNext);
|
||||||
|
deviceCreateInfo.pNext = &transformFeedbackFeatures;
|
||||||
|
m_transformFeedbackFeatureEnabled = true;
|
||||||
|
MGLOG_I("Enabled optional device extension: %s", VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!m_transformFeedbackFeatureEnabled) {
|
||||||
|
MGLOG_W("VK_EXT_transform_feedback is unavailable; transform feedback capture will not work");
|
||||||
|
}
|
||||||
|
|
||||||
deviceCreateInfo.enabledExtensionCount = static_cast<Uint32>(enabledDeviceExtensions.size());
|
deviceCreateInfo.enabledExtensionCount = static_cast<Uint32>(enabledDeviceExtensions.size());
|
||||||
deviceCreateInfo.ppEnabledExtensionNames = enabledDeviceExtensions.data();
|
deviceCreateInfo.ppEnabledExtensionNames = enabledDeviceExtensions.data();
|
||||||
MGLOG_I("Device feature support: robustBufferAccess=%s geometryShader=%s independentBlend=%s logicOp=%s shaderClipDistance=%s "
|
MGLOG_I("Device feature support: robustBufferAccess=%s geometryShader=%s independentBlend=%s logicOp=%s shaderClipDistance=%s "
|
||||||
@@ -8872,6 +9002,20 @@ void main() {
|
|||||||
MGLOG_W("VK_KHR_draw_indirect_count enabled but vkCmdDrawIndexedIndirectCount entry point is missing, will continue as if VK_KHR_draw_indirect_count is not supported!");
|
MGLOG_W("VK_KHR_draw_indirect_count enabled but vkCmdDrawIndexedIndirectCount entry point is missing, will continue as if VK_KHR_draw_indirect_count is not supported!");
|
||||||
m_drawIndirectCountExtensionEnabled = false;
|
m_drawIndirectCountExtensionEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_transformFeedbackFeatureEnabled) {
|
||||||
|
s_vkCmdBindTransformFeedbackBuffersEXT = reinterpret_cast<PFN_vkCmdBindTransformFeedbackBuffersEXT>(
|
||||||
|
vkGetDeviceProcAddr(m_device, "vkCmdBindTransformFeedbackBuffersEXT"));
|
||||||
|
s_vkCmdBeginTransformFeedbackEXT = reinterpret_cast<PFN_vkCmdBeginTransformFeedbackEXT>(
|
||||||
|
vkGetDeviceProcAddr(m_device, "vkCmdBeginTransformFeedbackEXT"));
|
||||||
|
s_vkCmdEndTransformFeedbackEXT = reinterpret_cast<PFN_vkCmdEndTransformFeedbackEXT>(
|
||||||
|
vkGetDeviceProcAddr(m_device, "vkCmdEndTransformFeedbackEXT"));
|
||||||
|
if (s_vkCmdBindTransformFeedbackBuffersEXT == nullptr || s_vkCmdBeginTransformFeedbackEXT == nullptr ||
|
||||||
|
s_vkCmdEndTransformFeedbackEXT == nullptr) {
|
||||||
|
MGLOG_W("VK_EXT_transform_feedback entry points missing; transform feedback capture disabled");
|
||||||
|
m_transformFeedbackFeatureEnabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
MGLOG_I("index type uint8 enabled: %s", m_indexTypeUint8ExtensionEnabled ? "true" : "false");
|
MGLOG_I("index type uint8 enabled: %s", m_indexTypeUint8ExtensionEnabled ? "true" : "false");
|
||||||
MGLOG_I("Logical device created.");
|
MGLOG_I("Logical device created.");
|
||||||
|
|
||||||
|
|||||||
@@ -455,6 +455,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint32 stride);
|
Uint32 stride);
|
||||||
static inline PFNDrawIndexedIndirectCountFunc s_vkCmdDrawIndexedIndirectCount = nullptr;
|
static inline PFNDrawIndexedIndirectCountFunc s_vkCmdDrawIndexedIndirectCount = nullptr;
|
||||||
|
|
||||||
|
// VK_EXT_transform_feedback (GL transform feedback capture)
|
||||||
|
Bool m_transformFeedbackFeatureEnabled = false;
|
||||||
|
static inline PFN_vkCmdBindTransformFeedbackBuffersEXT s_vkCmdBindTransformFeedbackBuffersEXT = nullptr;
|
||||||
|
static inline PFN_vkCmdBeginTransformFeedbackEXT s_vkCmdBeginTransformFeedbackEXT = nullptr;
|
||||||
|
static inline PFN_vkCmdEndTransformFeedbackEXT s_vkCmdEndTransformFeedbackEXT = nullptr;
|
||||||
|
// Counter buffers (one 4-byte slot per capture binding) let consecutive
|
||||||
|
// draws within one glBeginTransformFeedback append GL-style.
|
||||||
|
VkBufferObject m_xfbCounterBuffer;
|
||||||
|
// Non-zero while inside a GL Begin/End with at least one captured draw
|
||||||
|
// recorded; selects counter-buffer resume on the next captured draw.
|
||||||
|
Bool m_xfbCountersValid = false;
|
||||||
|
Uint64 m_xfbLastSeenGeneration = 0;
|
||||||
|
// Wraps a recorded draw with BeginTransformFeedbackEXT/EndTransformFeedbackEXT
|
||||||
|
// when GL transform feedback is active; binds capture buffers on demand.
|
||||||
|
Bool BeginXfbCaptureForDraw(FrameContext::FrameData& frame);
|
||||||
|
void EndXfbCaptureForDraw(FrameContext::FrameData& frame, Bool began);
|
||||||
|
|
||||||
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
||||||
|
|
||||||
VkBufferManager m_bufferManager;
|
VkBufferManager m_bufferManager;
|
||||||
|
|||||||
@@ -67,8 +67,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// While transform feedback is active the draw's primitive type must match
|
// While transform feedback is active the draw's primitive type must match
|
||||||
// the feedback primitive mode (GL 3.3 core 13.2.2).
|
// the feedback primitive mode (GL 3.3 core 13.2.2). With a geometry shader
|
||||||
if (MG_State::pGLContext->IsTransformFeedbackActive()) {
|
// the constraint moves to the shader's output primitive type instead, so
|
||||||
|
// the draw mode itself is unconstrained here.
|
||||||
|
if (MG_State::pGLContext->IsTransformFeedbackActive() &&
|
||||||
|
!(MG_State::pGLContext->GetTransformFeedbackProgram() &&
|
||||||
|
MG_State::pGLContext->GetTransformFeedbackProgram()->GetShaderIndexByStage(ShaderStage::Geometry) >= 0)) {
|
||||||
const GLenum feedbackMode = MG_State::pGLContext->GetTransformFeedbackPrimitiveMode();
|
const GLenum feedbackMode = MG_State::pGLContext->GetTransformFeedbackPrimitiveMode();
|
||||||
Bool compatible = false;
|
Bool compatible = false;
|
||||||
switch (feedbackMode) {
|
switch (feedbackMode) {
|
||||||
@@ -514,6 +518,18 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MG_State::pGLContext->EndTransformFeedback();
|
MG_State::pGLContext->EndTransformFeedback();
|
||||||
|
// Captured results must be visible to MapBuffer/GetBufferSubData after
|
||||||
|
// End; the capture targets are host-coherent GPU memory, so completing
|
||||||
|
// the GPU work is all that is required.
|
||||||
|
auto& backendGL = MG_Backend::gBackendFunctionsTable.GL;
|
||||||
|
if (backendGL.FenceSync && backendGL.ClientWaitSync) {
|
||||||
|
if (auto sync = backendGL.FenceSync()) {
|
||||||
|
backendGL.ClientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, ~0ull);
|
||||||
|
if (backendGL.DeleteSync) {
|
||||||
|
backendGL.DeleteSync(sync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
@@ -1932,6 +1932,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_MAX_VERTEX_STREAMS:
|
case GL_MAX_VERTEX_STREAMS:
|
||||||
*params = 1;
|
*params = 1;
|
||||||
break;
|
break;
|
||||||
|
case GL_TRANSFORM_FEEDBACK_ACTIVE:
|
||||||
|
*params = MG_State::pGLContext->IsTransformFeedbackActive() ? 1 : 0;
|
||||||
|
break;
|
||||||
|
case GL_TRANSFORM_FEEDBACK_PAUSED:
|
||||||
|
*params = 0;
|
||||||
|
break;
|
||||||
case GL_MAX_TEXTURE_IMAGE_UNITS:
|
case GL_MAX_TEXTURE_IMAGE_UNITS:
|
||||||
*params = dynamicParameters.MaxTextureImageUnits;
|
*params = dynamicParameters.MaxTextureImageUnits;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -231,6 +231,21 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
return m_resource.Bytes();
|
return m_resource.Bytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bool BufferObject::EnsureGpuResidentStorage() {
|
||||||
|
if (m_resource.IsGpuResident()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (m_size == 0 || g_bufferBackendOps == nullptr || g_bufferBackendOps->AcquirePersistentMap == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
void* base = g_bufferBackendOps->AcquirePersistentMap(*this);
|
||||||
|
if (base == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_resource.AdoptPersistentMap(base);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void* BufferObject::AcquireMemoryRange(Range1D range, Flags<BufferMappingAccessBit> access) {
|
void* BufferObject::AcquireMemoryRange(Range1D range, Flags<BufferMappingAccessBit> access) {
|
||||||
MOBILEGL_ASSERT(range.end <= m_size && range.start <= range.end,
|
MOBILEGL_ASSERT(range.end <= m_size && range.start <= range.end,
|
||||||
"AcquireMemoryRange out of bounds: range (%zu, %zu) exceeds m_size (%zu)", range.start,
|
"AcquireMemoryRange out of bounds: range (%zu, %zu) exceeds m_size (%zu)", range.start,
|
||||||
|
|||||||
@@ -133,6 +133,11 @@ namespace MobileGL {
|
|||||||
|
|
||||||
void* AcquireMemory(Bool markMapped, Bool read, Bool write);
|
void* AcquireMemory(Bool markMapped, Bool read, Bool write);
|
||||||
void* AcquireMemoryRange(Range1D range, Flags<BufferMappingAccessBit> access);
|
void* AcquireMemoryRange(Range1D range, Flags<BufferMappingAccessBit> access);
|
||||||
|
// Adopt backend host-visible coherent GPU storage as the source of truth
|
||||||
|
// (used for GPU-written targets like transform feedback capture, so
|
||||||
|
// MapBuffer/GetBufferSubData read real GPU results). No-op when already
|
||||||
|
// resident or when the backend declines.
|
||||||
|
Bool EnsureGpuResidentStorage();
|
||||||
void ReleaseMemory();
|
void ReleaseMemory();
|
||||||
void FlushMemoryRange(SizeT offset, SizeT length);
|
void FlushMemoryRange(SizeT offset, SizeT length);
|
||||||
|
|
||||||
|
|||||||
@@ -216,6 +216,7 @@ namespace MobileGL {
|
|||||||
m_transformFeedbackActive = true;
|
m_transformFeedbackActive = true;
|
||||||
m_transformFeedbackPrimitiveMode = primitiveMode;
|
m_transformFeedbackPrimitiveMode = primitiveMode;
|
||||||
m_transformFeedbackProgram = program;
|
m_transformFeedbackProgram = program;
|
||||||
|
++m_transformFeedbackGeneration;
|
||||||
}
|
}
|
||||||
void EndTransformFeedback() {
|
void EndTransformFeedback() {
|
||||||
m_transformFeedbackActive = false;
|
m_transformFeedbackActive = false;
|
||||||
@@ -226,6 +227,9 @@ namespace MobileGL {
|
|||||||
const SharedPtr<ProgramObject>& GetTransformFeedbackProgram() const {
|
const SharedPtr<ProgramObject>& GetTransformFeedbackProgram() const {
|
||||||
return m_transformFeedbackProgram;
|
return m_transformFeedbackProgram;
|
||||||
}
|
}
|
||||||
|
// Bumped on every BeginTransformFeedback; the backend uses it to
|
||||||
|
// distinguish "resume appending" from "fresh capture".
|
||||||
|
Uint64 GetTransformFeedbackGeneration() const { return m_transformFeedbackGeneration; }
|
||||||
|
|
||||||
// Framebuffer
|
// Framebuffer
|
||||||
void GenFramebufferNames(Uint number, Vector<Uint>& framebuffers);
|
void GenFramebufferNames(Uint number, Vector<Uint>& framebuffers);
|
||||||
@@ -262,6 +266,7 @@ namespace MobileGL {
|
|||||||
Bool m_transformFeedbackActive = false;
|
Bool m_transformFeedbackActive = false;
|
||||||
GLenum m_transformFeedbackPrimitiveMode = GL_POINTS;
|
GLenum m_transformFeedbackPrimitiveMode = GL_POINTS;
|
||||||
SharedPtr<ProgramObject> m_transformFeedbackProgram;
|
SharedPtr<ProgramObject> m_transformFeedbackProgram;
|
||||||
|
Uint64 m_transformFeedbackGeneration = 0;
|
||||||
TextureState m_textureState;
|
TextureState m_textureState;
|
||||||
ProgramState m_programState;
|
ProgramState m_programState;
|
||||||
RenderState m_renderState;
|
RenderState m_renderState;
|
||||||
|
|||||||
Reference in New Issue
Block a user