mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 12:48:32 +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;
|
||||
};
|
||||
|
||||
// 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
|
||||
// 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
|
||||
@@ -1128,6 +1285,41 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
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,
|
||||
ProgramFactory::CompileOptionFlags transformFlags) {
|
||||
if (input.empty()) {
|
||||
@@ -2173,7 +2365,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
// Apply position fixup if needed
|
||||
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 {
|
||||
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
|
||||
// clamped into [minLod, maxLod] = [0, 0] regardless of derivatives or bias).
|
||||
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 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_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_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
|
||||
// be host-coherent (Adreno host-visible memory is; requiring it keeps us portable).
|
||||
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
|
||||
// frontend adopts (and drops) the shadow only after this returns.
|
||||
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->storageSize = 0;
|
||||
resource->usageFlags = 0;
|
||||
|
||||
@@ -31,6 +31,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VmaMemoryUsage transientMemoryUsage = VMA_MEMORY_USAGE_AUTO;
|
||||
VmaAllocationCreateFlags transientAllocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
||||
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).
|
||||
|
||||
@@ -2581,6 +2581,7 @@ void main() {
|
||||
.transientMemoryUsage = VMA_MEMORY_USAGE_AUTO,
|
||||
.transientAllocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT,
|
||||
.transientPersistentMapping = true,
|
||||
.transformFeedbackUsageEnabled = m_transformFeedbackFeatureEnabled,
|
||||
});
|
||||
MOBILEGL_ASSERT(succeeded, "VkBufferManager initialization failed.");
|
||||
m_bufferManager.SetCopyCommandProvider(this);
|
||||
@@ -2745,6 +2746,7 @@ void main() {
|
||||
m_textureManager.reset();
|
||||
}
|
||||
m_vertexInputStateFactory.reset();
|
||||
m_xfbCounterBuffer.Destroy();
|
||||
m_bufferManager.Shutdown();
|
||||
|
||||
// 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& program = *MG_State::pGLContext->GetCurrentProgram();
|
||||
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
|
||||
// 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
|
||||
@@ -7292,6 +7299,100 @@ void main() {
|
||||
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) {
|
||||
auto& frame = m_frameContext.GetCurrent();
|
||||
|
||||
@@ -7303,11 +7404,13 @@ void main() {
|
||||
|
||||
VkCommandBuffer& commandBuffer = frame.commandBuffer;
|
||||
|
||||
const Bool xfbActive = BeginXfbCaptureForDraw(frame);
|
||||
vkCmdDraw(commandBuffer,
|
||||
payload.params.vertexCount,
|
||||
payload.params.instanceCount,
|
||||
payload.params.firstVertex,
|
||||
payload.params.firstInstance);
|
||||
EndXfbCaptureForDraw(frame, xfbActive);
|
||||
}
|
||||
|
||||
void VulkanRenderer::DrawElements(const DrawIndexedCmd& payload) {
|
||||
@@ -7334,12 +7437,14 @@ void main() {
|
||||
|
||||
VkCommandBuffer& commandBuffer = frame.commandBuffer;
|
||||
|
||||
const Bool xfbActive = BeginXfbCaptureForDraw(frame);
|
||||
vkCmdDrawIndexed(commandBuffer,
|
||||
payload.params.indexCount,
|
||||
payload.params.instanceCount,
|
||||
payload.params.firstIndex,
|
||||
payload.params.vertexOffset,
|
||||
payload.params.firstInstance);
|
||||
EndXfbCaptureForDraw(frame, xfbActive);
|
||||
}
|
||||
|
||||
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.ppEnabledExtensionNames = enabledDeviceExtensions.data();
|
||||
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!");
|
||||
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("Logical device created.");
|
||||
|
||||
|
||||
@@ -455,6 +455,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Uint32 stride);
|
||||
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;
|
||||
|
||||
VkBufferManager m_bufferManager;
|
||||
|
||||
@@ -67,8 +67,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
|
||||
// While transform feedback is active the draw's primitive type must match
|
||||
// the feedback primitive mode (GL 3.3 core 13.2.2).
|
||||
if (MG_State::pGLContext->IsTransformFeedbackActive()) {
|
||||
// the feedback primitive mode (GL 3.3 core 13.2.2). With a geometry shader
|
||||
// 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();
|
||||
Bool compatible = false;
|
||||
switch (feedbackMode) {
|
||||
@@ -514,6 +518,18 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return;
|
||||
}
|
||||
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
|
||||
|
||||
@@ -1932,6 +1932,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
case GL_MAX_VERTEX_STREAMS:
|
||||
*params = 1;
|
||||
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:
|
||||
*params = dynamicParameters.MaxTextureImageUnits;
|
||||
break;
|
||||
|
||||
@@ -231,6 +231,21 @@ namespace MobileGL::MG_State::GLState {
|
||||
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) {
|
||||
MOBILEGL_ASSERT(range.end <= m_size && range.start <= range.end,
|
||||
"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* 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 FlushMemoryRange(SizeT offset, SizeT length);
|
||||
|
||||
|
||||
@@ -216,6 +216,7 @@ namespace MobileGL {
|
||||
m_transformFeedbackActive = true;
|
||||
m_transformFeedbackPrimitiveMode = primitiveMode;
|
||||
m_transformFeedbackProgram = program;
|
||||
++m_transformFeedbackGeneration;
|
||||
}
|
||||
void EndTransformFeedback() {
|
||||
m_transformFeedbackActive = false;
|
||||
@@ -226,6 +227,9 @@ namespace MobileGL {
|
||||
const SharedPtr<ProgramObject>& GetTransformFeedbackProgram() const {
|
||||
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
|
||||
void GenFramebufferNames(Uint number, Vector<Uint>& framebuffers);
|
||||
@@ -262,6 +266,7 @@ namespace MobileGL {
|
||||
Bool m_transformFeedbackActive = false;
|
||||
GLenum m_transformFeedbackPrimitiveMode = GL_POINTS;
|
||||
SharedPtr<ProgramObject> m_transformFeedbackProgram;
|
||||
Uint64 m_transformFeedbackGeneration = 0;
|
||||
TextureState m_textureState;
|
||||
ProgramState m_programState;
|
||||
RenderState m_renderState;
|
||||
|
||||
Reference in New Issue
Block a user