mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 14:18:31 +09:00
Compare commits
19
Commits
598c5497b0
...
28c3cfc1d6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28c3cfc1d6 | ||
|
|
38e04eefae | ||
|
|
fd29cb914e | ||
|
|
38497174c8 | ||
|
|
b95fcb7bca | ||
|
|
5fce287de5 | ||
|
|
ae6949e459 | ||
|
|
5437947240 | ||
|
|
f38dbf018d | ||
|
|
2dcc15bb0e | ||
|
|
ff76af9df7 | ||
|
|
76f37a18e6 | ||
|
|
8d1a734c22 | ||
|
|
7d215028fb | ||
|
|
00534d8bbc | ||
|
|
d81a6a0998 | ||
|
|
9bf23d7ffd | ||
|
|
41e45f7d48 | ||
|
|
3dc6a1b6db |
@@ -234,8 +234,17 @@ namespace MobileGL {
|
||||
// drives capture from its draw recording instead (DirectVulkan). End is
|
||||
// called while the frontend capture state is still active, so the backend
|
||||
// can still see the capture program and buffer bindings.
|
||||
// GL_PATCH_VERTICES; ES 3.2 spells it the same way.
|
||||
void (*PatchParameteri)(GLenum pname, GLint value);
|
||||
void (*BeginTransformFeedback)(GLenum primitiveMode);
|
||||
void (*EndTransformFeedback)();
|
||||
// ARB_transform_feedback2. A backend that leaves these null keeps the single
|
||||
// implicit capture span the frontend has always modelled; the frontend state
|
||||
// (paused flag, per-object bindings) is tracked either way.
|
||||
void (*PauseTransformFeedback)();
|
||||
void (*ResumeTransformFeedback)();
|
||||
void (*BindTransformFeedback)(GLuint name);
|
||||
void (*DeleteTransformFeedback)(GLuint name);
|
||||
Int64 (*GetGpuTimestampNs)(); // glGetInteger64v(GL_TIMESTAMP); 0 if unsupported
|
||||
};
|
||||
struct GlobalBackendFunctionsTable {
|
||||
@@ -288,6 +297,13 @@ namespace MobileGL {
|
||||
Int MaxIntegerSamples = 1;
|
||||
Int MaxSamples = 1;
|
||||
Int MaxSampleMaskWords = 1;
|
||||
// Tessellation limits; defaults are the GL 4.0 core minimums.
|
||||
Int MaxPatchVertices = 32;
|
||||
Int MaxTessGenLevel = 64;
|
||||
// GL_MIN/MAX_PROGRAM_TEXTURE_GATHER_OFFSET. Defaults are the GL 4.0 core
|
||||
// minimums, which every ES 3.1 driver also guarantees.
|
||||
Int MinProgramTextureGatherOffset = -8;
|
||||
Int MaxProgramTextureGatherOffset = 7;
|
||||
Int MaxTextureImageUnits = 32;
|
||||
Int MaxVertexTextureImageUnits = 32;
|
||||
Int MaxComputeTextureImageUnits = 32;
|
||||
|
||||
@@ -910,7 +910,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// picks a whole different shader for draw_buffers without
|
||||
// explicit_attrib_location. DirectVulkan advertises both.
|
||||
E_GL_ARB_explicit_attrib_location, E_GL_ARB_texture_multisample,
|
||||
E_GL_ARB_shader_image_size};
|
||||
E_GL_ARB_shader_image_size,
|
||||
// Advertised with GL_NUM_PROGRAM_BINARY_FORMATS = 0, which the
|
||||
// extension explicitly permits. It is also the only thing that
|
||||
// exposes glProgramParameteri before GL 4.1.
|
||||
E_GL_ARB_get_program_binary};
|
||||
// Only advertised when the device driver actually has usable timer queries
|
||||
// (GL_EXT_disjoint_timer_query plus its entry points) and the
|
||||
// MOBILEGL_DISABLE_TIMERQUERY escape hatch is off.
|
||||
@@ -1030,8 +1034,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// Transform feedback is captured by the real ES driver rather than
|
||||
// reconstructed from the draw recording, so the frontend has to hand the
|
||||
// span boundaries over.
|
||||
funcsTable.GL.PatchParameteri = DirectGLES::PatchParameteri;
|
||||
funcsTable.GL.BeginTransformFeedback = XfbImpl::BeginTransformFeedback;
|
||||
funcsTable.GL.EndTransformFeedback = XfbImpl::EndTransformFeedback;
|
||||
funcsTable.GL.PauseTransformFeedback = XfbImpl::PauseTransformFeedback;
|
||||
funcsTable.GL.ResumeTransformFeedback = XfbImpl::ResumeTransformFeedback;
|
||||
funcsTable.GL.BindTransformFeedback = XfbImpl::BindTransformFeedback;
|
||||
funcsTable.GL.DeleteTransformFeedback = XfbImpl::DeleteTransformFeedback;
|
||||
funcsTableInitialized = true;
|
||||
}
|
||||
return funcsTable;
|
||||
@@ -1072,6 +1081,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
m_dynamicParameters.MaxIntegerSamples = m_GLESCapabilities.MaxIntegerSamples;
|
||||
m_dynamicParameters.MaxSamples = m_GLESCapabilities.MaxSamples;
|
||||
m_dynamicParameters.MaxSampleMaskWords = m_GLESCapabilities.MaxSampleMaskWords;
|
||||
m_dynamicParameters.MaxPatchVertices = m_GLESCapabilities.MaxPatchVertices;
|
||||
m_dynamicParameters.MaxTessGenLevel = m_GLESCapabilities.MaxTessGenLevel;
|
||||
m_dynamicParameters.MinProgramTextureGatherOffset = m_GLESCapabilities.MinProgramTextureGatherOffset;
|
||||
m_dynamicParameters.MaxProgramTextureGatherOffset = m_GLESCapabilities.MaxProgramTextureGatherOffset;
|
||||
// Clamp the advertised sampler limits the same way the DirectVulkan backend does: per-stage
|
||||
// GL_MAX_TEXTURE_IMAGE_UNITS must never exceed host-side fixed arrays sized off it (e.g.
|
||||
// Minecraft's 128-entry Blaze3D GlStateManager.TEXTURES[], iterated by Iris), and the combined
|
||||
|
||||
@@ -295,6 +295,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
}
|
||||
|
||||
// Called once the storage-buffer points are bound and the draw/dispatch is about to
|
||||
// go out: whatever the shader writes there lands in the ES driver's buffers, behind
|
||||
// the frontend's CPU shadow. Flagging them makes the next MapBuffer/GetBufferSubData
|
||||
// pull the real contents back (BufferObject::SyncGpuWrites).
|
||||
void MarkShaderStorageBuffersGpuWritten() {
|
||||
const SizeT bindingPointCnt =
|
||||
MG_State::pGLContext->GetTouchedBufferBindingPointCount(BufferTarget::ShaderStorage);
|
||||
for (SizeT i = 0; i < bindingPointCnt; ++i) {
|
||||
const auto& obj =
|
||||
MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::ShaderStorage, i).GetBoundObject();
|
||||
if (obj) obj->MarkGpuWritten();
|
||||
}
|
||||
}
|
||||
|
||||
void SyncBoundBuffer(BufferTarget target, GLenum glTarget) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
@@ -367,6 +381,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// BindCurrentProgramWithResources binds no SSBO points, so this is their sole draw-path
|
||||
// binder (e.g. Flywheel's indirect vertex shaders pull instance data from storage buffers).
|
||||
SyncBufferBindingPoints(BufferTarget::ShaderStorage, GL_SHADER_STORAGE_BUFFER);
|
||||
MarkShaderStorageBuffersGpuWritten();
|
||||
}
|
||||
|
||||
void SyncComputeBuffers(Bool includeDispatchIndirectBuffer) {
|
||||
@@ -376,6 +391,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
ProcessDeferredBufferReleases();
|
||||
SyncBufferBindingPoints(BufferTarget::Uniform, GL_UNIFORM_BUFFER);
|
||||
SyncBufferBindingPoints(BufferTarget::ShaderStorage, GL_SHADER_STORAGE_BUFFER);
|
||||
MarkShaderStorageBuffersGpuWritten();
|
||||
if (includeDispatchIndirectBuffer) {
|
||||
SyncBoundBuffer(BufferTarget::DispatchIndirect, GL_DISPATCH_INDIRECT_BUFFER);
|
||||
}
|
||||
@@ -391,6 +407,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// the capture buffers bound when Begin is issued, and both of those only become true
|
||||
// once PrepareForDraw has run. A span that never draws therefore never touches the
|
||||
// driver at all, which is also what the GL semantics amount to.
|
||||
//
|
||||
// Transform feedback objects (ARB_transform_feedback2) are ES 3.0 core, so each
|
||||
// frontend object gets one of the driver's: a paused span lives inside the ES object,
|
||||
// which is the only way several of them can be paused at once - and the only reason
|
||||
// the default object alone would not do.
|
||||
namespace XfbImpl {
|
||||
namespace {
|
||||
struct XfbCaptureTarget {
|
||||
@@ -400,10 +421,161 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
SizeT end = 0;
|
||||
};
|
||||
|
||||
Bool g_xfbPending = false; // frontend Begin seen, driver capture not started yet
|
||||
Bool g_xfbStarted = false; // driver capture running
|
||||
GLenum g_xfbPrimitiveMode = GL_POINTS;
|
||||
Vector<XfbCaptureTarget> g_xfbTargets;
|
||||
// Per frontend transform feedback object. The default object (name 0) maps to
|
||||
// the driver's default object (id 0) and is always present.
|
||||
struct XfbObjectState {
|
||||
GLuint esId = 0;
|
||||
Bool pending = false; // frontend Begin seen, driver capture not started yet
|
||||
Bool started = false; // driver capture running
|
||||
Bool paused = false; // frontend Pause seen and not yet resumed
|
||||
GLenum primitiveMode = GL_POINTS;
|
||||
Vector<XfbCaptureTarget> targets;
|
||||
// Set for a layout ES cannot express (gl_SkipComponents / gl_NextBuffer):
|
||||
// the driver captures gap-free records into the scratch buffer below and
|
||||
// End scatters them into `targets`.
|
||||
Bool scattered = false;
|
||||
SharedPtr<MG_State::GLState::ProgramObject> scatterProgram;
|
||||
SizeT scatterCapacityVertices = 0;
|
||||
};
|
||||
|
||||
// One scratch ES buffer serves every scattered capture: only one span can be
|
||||
// recording at a time (the driver would reject a second Begin), so its contents
|
||||
// are consumed by the End that follows.
|
||||
GLuint g_scatterBufferId = 0;
|
||||
SizeT g_scatterBufferSize = 0;
|
||||
|
||||
UnorderedMap<GLuint, XfbObjectState> g_xfbObjects;
|
||||
GLuint g_currentXfbName = 0;
|
||||
|
||||
XfbObjectState& CurrentXfb() {
|
||||
return g_xfbObjects[g_currentXfbName];
|
||||
}
|
||||
|
||||
Bool AreTransformFeedbackObjectsSupported() {
|
||||
return g_GLESFuncs.glGenTransformFeedbacks != nullptr &&
|
||||
g_GLESFuncs.glBindTransformFeedback != nullptr &&
|
||||
g_GLESFuncs.glDeleteTransformFeedbacks != nullptr &&
|
||||
g_GLESFuncs.glPauseTransformFeedback != nullptr &&
|
||||
g_GLESFuncs.glResumeTransformFeedback != nullptr;
|
||||
}
|
||||
|
||||
// Mirrors one capture span's results into the frontend CPU shadows. The GPU wrote
|
||||
// the capture buffers behind the frontend's back, so the shadows that back
|
||||
// MapBuffer/GetBufferSubData still hold the pre-draw bytes. Buffers whose storage
|
||||
// the backend already owns (coherent persistent map) need nothing: reads resolve
|
||||
// against that storage directly.
|
||||
void ReadbackCapturedRanges(Vector<XfbCaptureTarget>& targets) {
|
||||
if (g_GLESFuncs.glMapBufferRange != nullptr && g_GLESFuncs.glUnmapBuffer != nullptr) {
|
||||
for (const auto& target : targets) {
|
||||
if (!target.buffer || target.buffer->IsBackendPersistentMapped()) continue;
|
||||
const SizeT size = target.end - target.start;
|
||||
BufferImpl::BindBufferId(BufferImpl::TempBufferTarget, target.backendId);
|
||||
void* mapped = g_GLESFuncs.glMapBufferRange(BufferImpl::TempBufferTarget,
|
||||
static_cast<GLintptr>(target.start),
|
||||
static_cast<GLsizeiptr>(size), GL_MAP_READ_BIT);
|
||||
if (mapped == nullptr) {
|
||||
MGLOG_E("EndTransformFeedback: failed to map backend buffer %u for capture readback",
|
||||
target.backendId);
|
||||
continue;
|
||||
}
|
||||
target.buffer->WritebackFromBackend({mapped, size}, target.start);
|
||||
g_GLESFuncs.glUnmapBuffer(BufferImpl::TempBufferTarget);
|
||||
}
|
||||
}
|
||||
targets.clear();
|
||||
}
|
||||
|
||||
// Binds a scratch buffer, sized for `capacityVertices` gap-free records, to
|
||||
// capture point 0 in place of the application's buffers. Returns false when the
|
||||
// scratch storage cannot be provided, in which case the caller falls back to the
|
||||
// direct binding (which produces a wrong layout, but is what happened before).
|
||||
Bool BindScatterCaptureBuffer(SizeT packedStride, SizeT capacityVertices) {
|
||||
if (packedStride == 0 || capacityVertices == 0) return false;
|
||||
if (g_GLESFuncs.glGenBuffers == nullptr || g_GLESFuncs.glBufferData == nullptr) return false;
|
||||
const SizeT required = packedStride * capacityVertices;
|
||||
if (g_scatterBufferId == 0) {
|
||||
g_GLESFuncs.glGenBuffers(1, &g_scatterBufferId);
|
||||
if (g_scatterBufferId == 0) return false;
|
||||
g_scatterBufferSize = 0;
|
||||
}
|
||||
if (g_scatterBufferSize < required) {
|
||||
BufferImpl::BindBufferId(BufferImpl::TempBufferTarget, g_scatterBufferId);
|
||||
g_GLESFuncs.glBufferData(BufferImpl::TempBufferTarget, static_cast<GLsizeiptr>(required), nullptr,
|
||||
GL_DYNAMIC_COPY);
|
||||
g_scatterBufferSize = required;
|
||||
}
|
||||
// Point 0 carries every captured varying (the ES capture is INTERLEAVED); the
|
||||
// other points must be cleared or the driver would still write the app's buffers.
|
||||
BufferImpl::BindBufferRangeCached(GL_TRANSFORM_FEEDBACK_BUFFER, 0, g_scatterBufferId, 0,
|
||||
static_cast<GLsizeiptr>(required));
|
||||
const SizeT pointCount =
|
||||
MG_State::pGLContext->GetTouchedBufferBindingPointCount(BufferTarget::TransformFeedback);
|
||||
for (SizeT i = 1; i < pointCount; ++i) {
|
||||
BufferImpl::BindBufferBaseCached(GL_TRANSFORM_FEEDBACK_BUFFER, static_cast<Uint>(i), 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Distributes the gap-free records the driver captured into the application's
|
||||
// buffers at the offsets the GL layout asks for. Only the bytes a varying actually
|
||||
// occupies are written, so the holes gl_SkipComponents asks for keep whatever the
|
||||
// application had put there - which is the whole point of the feature.
|
||||
void ScatterCapturedRecords(XfbObjectState& xfb) {
|
||||
const auto& program = xfb.scatterProgram;
|
||||
if (!program || xfb.targets.empty()) return;
|
||||
if (g_GLESFuncs.glMapBufferRange == nullptr || g_GLESFuncs.glUnmapBuffer == nullptr) return;
|
||||
|
||||
const SizeT packedStride = program->GetTransformFeedbackPackedStride();
|
||||
const SizeT vertices = std::min<SizeT>(
|
||||
static_cast<SizeT>(MG_State::pGLContext->GetTransformFeedbackCapturedVertices()),
|
||||
xfb.scatterCapacityVertices);
|
||||
if (packedStride == 0 || vertices == 0) return;
|
||||
|
||||
BufferImpl::BindBufferId(BufferImpl::TempBufferTarget, g_scatterBufferId);
|
||||
const void* packed = g_GLESFuncs.glMapBufferRange(BufferImpl::TempBufferTarget, 0,
|
||||
static_cast<GLsizeiptr>(packedStride * vertices),
|
||||
GL_MAP_READ_BIT);
|
||||
if (packed == nullptr) {
|
||||
MGLOG_E("EndTransformFeedback: failed to map the scatter capture buffer");
|
||||
return;
|
||||
}
|
||||
|
||||
// One staged copy per destination buffer: start from what the application had
|
||||
// (the shadow is authoritative - uploads go shadow -> ES, and previous captures
|
||||
// were mirrored back into it), patch the captured varyings in, then push the
|
||||
// whole range down once.
|
||||
for (SizeT targetIndex = 0; targetIndex < xfb.targets.size(); ++targetIndex) {
|
||||
const auto& target = xfb.targets[targetIndex];
|
||||
if (!target.buffer) continue;
|
||||
const SizeT stride = program->GetTransformFeedbackStride(static_cast<Uint32>(targetIndex));
|
||||
if (stride == 0) continue;
|
||||
const SizeT rangeBytes = target.end - target.start;
|
||||
Vector<Uint8> staged(rangeBytes);
|
||||
Memcpy(staged.data(), target.buffer->MappedData() + target.start, rangeBytes);
|
||||
|
||||
for (const auto& varying : program->GetTransformFeedbackVaryings()) {
|
||||
if (varying.bufferIndex != targetIndex) continue;
|
||||
for (SizeT v = 0; v < vertices; ++v) {
|
||||
const SizeT dstOffset = v * stride + varying.offsetBytes;
|
||||
if (dstOffset + varying.byteSize > rangeBytes) break;
|
||||
Memcpy(staged.data() + dstOffset,
|
||||
static_cast<const Uint8*>(packed) + v * packedStride + varying.packedOffsetBytes,
|
||||
varying.byteSize);
|
||||
}
|
||||
}
|
||||
|
||||
target.buffer->WritebackFromBackend({staged.data(), rangeBytes}, target.start);
|
||||
if (g_GLESFuncs.glBufferSubData != nullptr) {
|
||||
BufferImpl::BindBufferId(BufferImpl::TempBufferTarget, target.backendId);
|
||||
g_GLESFuncs.glBufferSubData(BufferImpl::TempBufferTarget,
|
||||
static_cast<GLintptr>(target.start),
|
||||
static_cast<GLsizeiptr>(rangeBytes), staged.data());
|
||||
}
|
||||
BufferImpl::BindBufferId(BufferImpl::TempBufferTarget, g_scatterBufferId);
|
||||
}
|
||||
g_GLESFuncs.glUnmapBuffer(BufferImpl::TempBufferTarget);
|
||||
xfb.targets.clear();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Bool AreTransformFeedbacksSupported() {
|
||||
@@ -414,17 +586,23 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
void BeginTransformFeedback(GLenum primitiveMode) {
|
||||
if (!AreTransformFeedbacksSupported()) return;
|
||||
g_xfbPrimitiveMode = primitiveMode;
|
||||
g_xfbPending = true;
|
||||
g_xfbStarted = false;
|
||||
g_xfbTargets.clear();
|
||||
auto& xfb = CurrentXfb();
|
||||
xfb.primitiveMode = primitiveMode;
|
||||
xfb.pending = true;
|
||||
xfb.started = false;
|
||||
xfb.paused = false;
|
||||
xfb.targets.clear();
|
||||
}
|
||||
|
||||
// Tail of PrepareForDraw: the program is bound and every buffer the draw needs
|
||||
// is up to date, so the capture buffers can be bound and the span opened.
|
||||
void StartPendingTransformFeedback() {
|
||||
if (!g_xfbPending) return;
|
||||
g_xfbPending = false;
|
||||
auto& xfb = CurrentXfb();
|
||||
// A span that was paused before its first draw must not open here: the draw is
|
||||
// not captured, and opening the span would also subject it to the capture
|
||||
// primitive-mode rule the paused draw is exempt from.
|
||||
if (!xfb.pending || xfb.paused) return;
|
||||
xfb.pending = false;
|
||||
const auto& program = MG_State::pGLContext->GetTransformFeedbackProgram();
|
||||
if (!program) return;
|
||||
|
||||
@@ -443,51 +621,103 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
const SizeT start = std::min(range.start, bufferObject->GetSize());
|
||||
const SizeT end = std::min(range.end, bufferObject->GetSize());
|
||||
if (end <= start) continue;
|
||||
g_xfbTargets.push_back({bufferObject, backendResource->id, start, end});
|
||||
xfb.targets.push_back({bufferObject, backendResource->id, start, end});
|
||||
}
|
||||
|
||||
BufferImpl::SyncBufferBindingPoints(BufferTarget::TransformFeedback, GL_TRANSFORM_FEEDBACK_BUFFER);
|
||||
g_GLESFuncs.glBeginTransformFeedback(g_xfbPrimitiveMode);
|
||||
g_xfbStarted = true;
|
||||
|
||||
// A layout with holes or several interleaved buffers is not expressible on ES:
|
||||
// capture gap-free into scratch storage and place the records at End instead.
|
||||
xfb.scattered = false;
|
||||
xfb.scatterProgram.reset();
|
||||
xfb.scatterCapacityVertices = 0;
|
||||
if (program->NeedsScatteredTransformFeedbackCapture()) {
|
||||
SizeT capacityVertices = ~SizeT(0);
|
||||
for (SizeT i = 0; i < xfb.targets.size(); ++i) {
|
||||
const SizeT stride = program->GetTransformFeedbackStride(static_cast<Uint32>(i));
|
||||
if (stride == 0) continue;
|
||||
capacityVertices =
|
||||
std::min<SizeT>(capacityVertices, (xfb.targets[i].end - xfb.targets[i].start) / stride);
|
||||
}
|
||||
if (capacityVertices == ~SizeT(0)) capacityVertices = 0;
|
||||
if (BindScatterCaptureBuffer(program->GetTransformFeedbackPackedStride(), capacityVertices)) {
|
||||
xfb.scattered = true;
|
||||
xfb.scatterProgram = program;
|
||||
xfb.scatterCapacityVertices = capacityVertices;
|
||||
}
|
||||
}
|
||||
|
||||
g_GLESFuncs.glBeginTransformFeedback(xfb.primitiveMode);
|
||||
xfb.started = true;
|
||||
}
|
||||
|
||||
void EndTransformFeedback() {
|
||||
g_xfbPending = false;
|
||||
if (!g_xfbStarted) return;
|
||||
g_xfbStarted = false;
|
||||
auto& xfb = CurrentXfb();
|
||||
xfb.pending = false;
|
||||
xfb.paused = false;
|
||||
if (!xfb.started) return;
|
||||
xfb.started = false;
|
||||
g_GLESFuncs.glEndTransformFeedback();
|
||||
|
||||
// The GPU wrote the capture buffers behind the frontend's back, so the CPU
|
||||
// shadows that back MapBuffer/GetBufferSubData still hold the pre-draw bytes.
|
||||
// Mirror the captured ranges into them. Buffers whose storage the backend
|
||||
// already owns (coherent persistent map) need nothing: reads resolve against
|
||||
// that storage directly.
|
||||
if (g_GLESFuncs.glMapBufferRange != nullptr && g_GLESFuncs.glUnmapBuffer != nullptr) {
|
||||
for (const auto& target : g_xfbTargets) {
|
||||
if (!target.buffer || target.buffer->IsBackendPersistentMapped()) continue;
|
||||
const SizeT size = target.end - target.start;
|
||||
BufferImpl::BindBufferId(BufferImpl::TempBufferTarget, target.backendId);
|
||||
void* mapped = g_GLESFuncs.glMapBufferRange(BufferImpl::TempBufferTarget,
|
||||
static_cast<GLintptr>(target.start),
|
||||
static_cast<GLsizeiptr>(size), GL_MAP_READ_BIT);
|
||||
if (mapped == nullptr) {
|
||||
MGLOG_E("EndTransformFeedback: failed to map backend buffer %u for capture readback",
|
||||
target.backendId);
|
||||
continue;
|
||||
}
|
||||
target.buffer->WritebackFromBackend({mapped, size}, target.start);
|
||||
g_GLESFuncs.glUnmapBuffer(BufferImpl::TempBufferTarget);
|
||||
}
|
||||
if (xfb.scattered) {
|
||||
ScatterCapturedRecords(xfb);
|
||||
xfb.scattered = false;
|
||||
xfb.scatterProgram.reset();
|
||||
} else {
|
||||
ReadbackCapturedRanges(xfb.targets);
|
||||
}
|
||||
g_xfbTargets.clear();
|
||||
}
|
||||
|
||||
// The ES context went away (or is being torn down): the span, its buffer ids and
|
||||
// the frontend objects it pinned all belonged to it.
|
||||
void PauseTransformFeedback() {
|
||||
auto& xfb = CurrentXfb();
|
||||
xfb.paused = true;
|
||||
// A span the driver never opened (paused before the first draw) has nothing to
|
||||
// pause; the flag above is what holds the deferred Begin back until the resume.
|
||||
if (!xfb.started || g_GLESFuncs.glPauseTransformFeedback == nullptr) return;
|
||||
g_GLESFuncs.glPauseTransformFeedback();
|
||||
}
|
||||
|
||||
void ResumeTransformFeedback() {
|
||||
auto& xfb = CurrentXfb();
|
||||
xfb.paused = false;
|
||||
if (!xfb.started || g_GLESFuncs.glResumeTransformFeedback == nullptr) return;
|
||||
g_GLESFuncs.glResumeTransformFeedback();
|
||||
}
|
||||
|
||||
void BindTransformFeedback(GLuint name) {
|
||||
if (!AreTransformFeedbackObjectsSupported()) {
|
||||
// Without driver objects there is only the default span; keep the frontend
|
||||
// name so the bookkeeping below stays consistent.
|
||||
g_currentXfbName = name;
|
||||
return;
|
||||
}
|
||||
auto& xfb = g_xfbObjects[name];
|
||||
if (name != 0 && xfb.esId == 0) {
|
||||
g_GLESFuncs.glGenTransformFeedbacks(1, &xfb.esId);
|
||||
}
|
||||
g_GLESFuncs.glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, xfb.esId);
|
||||
g_currentXfbName = name;
|
||||
}
|
||||
|
||||
void DeleteTransformFeedback(GLuint name) {
|
||||
const auto it = g_xfbObjects.find(name);
|
||||
if (it == g_xfbObjects.end()) return;
|
||||
if (it->second.esId != 0 && g_GLESFuncs.glDeleteTransformFeedbacks != nullptr) {
|
||||
g_GLESFuncs.glDeleteTransformFeedbacks(1, &it->second.esId);
|
||||
}
|
||||
g_xfbObjects.erase(it);
|
||||
// The frontend reverts to the default object when the bound one is deleted.
|
||||
if (g_currentXfbName == name) {
|
||||
BindTransformFeedback(0);
|
||||
}
|
||||
}
|
||||
|
||||
// The ES context went away (or is being torn down): the spans, their buffer ids, the
|
||||
// driver objects and the frontend objects they pinned all belonged to it.
|
||||
void OnBackendContextDestroyed() {
|
||||
g_xfbPending = false;
|
||||
g_xfbStarted = false;
|
||||
g_xfbTargets.clear();
|
||||
g_xfbObjects.clear();
|
||||
g_currentXfbName = 0;
|
||||
g_scatterBufferId = 0;
|
||||
g_scatterBufferSize = 0;
|
||||
}
|
||||
} // namespace XfbImpl
|
||||
|
||||
@@ -1383,6 +1613,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
const GLenum targetGL = TextureImpl::ConvertTextureTargetToBackendGLEnum(target);
|
||||
|
||||
// A texture whose mip chain does not satisfy the filter's completeness
|
||||
// rules samples as (0, 0, 0, 1). The ES driver cannot work that out for
|
||||
// itself here: the backend texture is immutable storage, so a level the
|
||||
// application redefined at the wrong size never reached it. Leaving the
|
||||
// native target unbound produces exactly the incomplete-texture result.
|
||||
const auto& effectiveSampler = textureUnit.GetSamplerObject()
|
||||
? textureUnit.GetSamplerObject()
|
||||
: textureObject->GetSamplerObject();
|
||||
const Bool mipmappedFilter =
|
||||
effectiveSampler && effectiveSampler->GetMipmapMode() != SamplerMipmapMode::None;
|
||||
if (!MG_State::GLState::IsMipmapCompleteForFilter(textureObject.get(), mipmappedFilter)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Bind texture object
|
||||
const auto& backendTextureIt = TextureImpl::g_backendTextureObjects.find(textureObject.get());
|
||||
if (backendTextureIt == TextureImpl::g_backendTextureObjects.end()) continue;
|
||||
@@ -3709,6 +3953,83 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
}
|
||||
|
||||
// ES has no colour-renderable three-channel float format, and its glGenerateMipmap
|
||||
// requires one, so it rejects GL_RGB16F and GL_RGB32F outright where every desktop
|
||||
// driver accepts them. Both store a plain array of floats, and a format the driver
|
||||
// cannot render into is a format nothing can have rendered into - so the frontend's own
|
||||
// copy of the texels is the authority, and the chain can be filtered there and carried
|
||||
// down by the ordinary upload path. Returns false for anything else, leaving the
|
||||
// driver's answer (including its error) in place.
|
||||
static Bool GenerateThreeChannelFloatMipmapOnCpu(
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& texture) {
|
||||
if (!texture) return false;
|
||||
const TextureInternalFormat format = texture->GetFormat();
|
||||
const Bool isHalf = format == TextureInternalFormat::RGB16F;
|
||||
if (!isHalf && format != TextureInternalFormat::RGB32F) return false;
|
||||
|
||||
auto* mipmapTexture = MG_State::GLState::AsMipmapTexture(texture.get());
|
||||
if (mipmapTexture == nullptr) return false;
|
||||
const Uint levelCount = mipmapTexture->GetMipmapLevelCount();
|
||||
constexpr Int kChannels = 3;
|
||||
|
||||
for (const auto uploadTarget : texture->GetUploadTargets()) {
|
||||
for (Uint level = 1; level < levelCount; ++level) {
|
||||
const IntVec3 srcSize = mipmapTexture->GetMipmapTexelSize(uploadTarget, level - 1);
|
||||
const IntVec3 dstSize = mipmapTexture->GetMipmapTexelSize(uploadTarget, level);
|
||||
if (srcSize.x() <= 0 || srcSize.y() <= 0 || dstSize.x() <= 0 || dstSize.y() <= 0) return false;
|
||||
auto* src = static_cast<Uint8*>(mipmapTexture->MapMipmapData(uploadTarget, level - 1));
|
||||
auto* dst = static_cast<Uint8*>(mipmapTexture->MapMipmapData(uploadTarget, level));
|
||||
if (src == nullptr || dst == nullptr) return false;
|
||||
|
||||
const SizeT componentBytes = isHalf ? sizeof(Uint16) : sizeof(Float);
|
||||
const SizeT texelBytes = componentBytes * kChannels;
|
||||
const auto load = [&](const Uint8* base, Int x, Int y, Int channel) {
|
||||
const Uint8* texel = base + (static_cast<SizeT>(y) * srcSize.x() + x) * texelBytes +
|
||||
channel * componentBytes;
|
||||
if (isHalf) {
|
||||
Uint16 bits = 0;
|
||||
Memcpy(&bits, texel, sizeof(bits));
|
||||
return MG_Util::DecodeHalfBitsToFloat(bits);
|
||||
}
|
||||
Float value = 0.0f;
|
||||
Memcpy(&value, texel, sizeof(value));
|
||||
return value;
|
||||
};
|
||||
|
||||
// Box filter over the 2x2 source footprint, clamped where a dimension is
|
||||
// already 1 (GL 4.6 core 8.14.4 leaves the exact filter to the implementation
|
||||
// and this is the one it describes for power-of-two levels).
|
||||
for (Int y = 0; y < dstSize.y(); ++y) {
|
||||
for (Int x = 0; x < dstSize.x(); ++x) {
|
||||
const Int x0 = std::min(x * 2, srcSize.x() - 1);
|
||||
const Int x1 = std::min(x * 2 + 1, srcSize.x() - 1);
|
||||
const Int y0 = std::min(y * 2, srcSize.y() - 1);
|
||||
const Int y1 = std::min(y * 2 + 1, srcSize.y() - 1);
|
||||
for (Int channel = 0; channel < kChannels; ++channel) {
|
||||
const Float average = 0.25f * (load(src, x0, y0, channel) + load(src, x1, y0, channel) +
|
||||
load(src, x0, y1, channel) + load(src, x1, y1, channel));
|
||||
Uint8* texel = dst + (static_cast<SizeT>(y) * dstSize.x() + x) * texelBytes +
|
||||
channel * componentBytes;
|
||||
if (isHalf) {
|
||||
const Uint16 bits = MG_Util::EncodeFloatToHalfBits(average);
|
||||
Memcpy(texel, &bits, sizeof(bits));
|
||||
} else {
|
||||
Memcpy(texel, &average, sizeof(average));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mipmapTexture->MarkStorageDirty(uploadTarget, level, true);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PatchParameteri(GLenum pname, GLint value) {
|
||||
if (g_GLESFuncs.glPatchParameteri == nullptr) return;
|
||||
g_GLESFuncs.glPatchParameteri(pname, value);
|
||||
}
|
||||
|
||||
void GenerateMipmap(GLenum target) {
|
||||
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER
|
||||
DebugImpl::OpenGLScopeMarker marker(__func__);
|
||||
@@ -3718,9 +4039,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
auto& slot = unit.GetBindingSlot(MG_Util::ConvertGLEnumToTextureTarget(target));
|
||||
auto& texture = slot.GetBoundObject();
|
||||
MOBILEGL_ASSERT(texture != nullptr, "GenerateMipmap requires a bound texture.");
|
||||
if (texture->GetFormat() == TextureInternalFormat::R11FG11FB10F || IsDepthOnlyFormat(texture->GetFormat())) {
|
||||
if (texture->GetFormat() == TextureInternalFormat::R11FG11FB10F || IsDepthOnlyFormat(texture->GetFormat()) ||
|
||||
texture->GetFormat() == TextureInternalFormat::RGB16F ||
|
||||
texture->GetFormat() == TextureInternalFormat::RGB32F) {
|
||||
EnsureGenerateMipmapStorageAllocated(texture);
|
||||
}
|
||||
// Filtered on the CPU before the backend sync, so the dirty levels ride down with it.
|
||||
if (GenerateThreeChannelFloatMipmapOnCpu(texture)) {
|
||||
TextureImpl::SyncTextureObjectToBackend(texture);
|
||||
return;
|
||||
}
|
||||
auto& backendTexture = TextureImpl::SyncTextureObjectToBackend(texture);
|
||||
|
||||
if (IsDepthOnlyFormat(texture->GetFormat())) {
|
||||
|
||||
@@ -169,10 +169,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// begin is deferred to the first draw of the span (ES needs the capturing
|
||||
// program current and the capture buffers bound), and the end also mirrors the
|
||||
// captured bytes back into the frontend buffer shadows.
|
||||
void PatchParameteri(GLenum pname, GLint value);
|
||||
|
||||
namespace XfbImpl {
|
||||
Bool AreTransformFeedbacksSupported();
|
||||
void BeginTransformFeedback(GLenum primitiveMode);
|
||||
void EndTransformFeedback();
|
||||
void PauseTransformFeedback();
|
||||
void ResumeTransformFeedback();
|
||||
void BindTransformFeedback(GLuint name);
|
||||
void DeleteTransformFeedback(GLuint name);
|
||||
void OnBackendContextDestroyed();
|
||||
} // namespace XfbImpl
|
||||
|
||||
|
||||
@@ -630,6 +630,32 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
resource->syncedChangeSerial = bufferObject.GetChangeSerial();
|
||||
}
|
||||
|
||||
// A shader wrote this buffer through a storage/atomic-counter binding, so the ES
|
||||
// driver's copy is ahead of the frontend shadow. Pull the whole thing back so
|
||||
// MapBuffer/GetBufferSubData/CopyBufferSubData see the real results.
|
||||
void Ops_ReadbackFromGpu(BufferObject& bufferObject) {
|
||||
auto* resource = ResourceOf(bufferObject);
|
||||
if (!resource || resource->id == 0 || !resource->storageInitialized) return;
|
||||
if (resource->persistentMapped) return; // shadow already IS the GPU storage
|
||||
if (!CanTouchGLNow() || resource->contextGeneration != g_bufferContextGeneration) return;
|
||||
if (!g_GLESFuncs.glMapBufferRange || !g_GLESFuncs.glUnmapBuffer) return;
|
||||
const SizeT size = std::min<SizeT>(bufferObject.GetSize(), resource->storageSize);
|
||||
if (size == 0) return;
|
||||
|
||||
BindBufferId(TempBufferTarget, resource->id);
|
||||
void* mapped = g_GLESFuncs.glMapBufferRange(TempBufferTarget, 0, static_cast<GLsizeiptr>(size),
|
||||
GL_MAP_READ_BIT);
|
||||
if (mapped == nullptr) {
|
||||
MGLOG_E("Ops_ReadbackFromGpu: glMapBufferRange(read) failed for buffer %u", resource->id);
|
||||
return;
|
||||
}
|
||||
bufferObject.WritebackFromBackend({mapped, size}, 0);
|
||||
g_GLESFuncs.glUnmapBuffer(TempBufferTarget);
|
||||
// The shadow now matches the backend byte for byte; without this the next
|
||||
// draw would see a newer change serial and re-upload the readback over it.
|
||||
resource->syncedChangeSerial = bufferObject.GetChangeSerial();
|
||||
}
|
||||
|
||||
void Ops_OnDestroy(SharedPtr<BackendBufferResource>&& resource) {
|
||||
if (!resource) return;
|
||||
auto* glesResource = static_cast<GLESBufferResource*>(resource.get());
|
||||
@@ -662,6 +688,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
.FlushMappedRange = Ops_FlushMappedRange,
|
||||
.OnDestroy = Ops_OnDestroy,
|
||||
.AcquirePersistentMap = Ops_AcquirePersistentMap,
|
||||
.ReadbackFromGpu = Ops_ReadbackFromGpu,
|
||||
};
|
||||
} // namespace
|
||||
|
||||
@@ -3357,14 +3384,18 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
// ES has no rectangle sampler, and SPIRV-Cross refuses the whole module rather
|
||||
// than approximating one. Where every use takes integer texel coordinates a
|
||||
// rectangle image is indistinguishable from a 2D one, so rewrite the type and let
|
||||
// it through; the pass declines anything it cannot convert exactly.
|
||||
// than approximating one. Rewriting the type to 2D is exact for a lookup that
|
||||
// takes integer texel coordinates and needs the coordinate divided by the
|
||||
// texture size for one that does not - see NormalizeRectSamplerCoordinates
|
||||
// below, which the ESSL the transpiler produces goes through. The pass declines
|
||||
// anything neither step can convert.
|
||||
Vector<unsigned int> rectLoweredSpirv;
|
||||
Bool loweredRectImages = false;
|
||||
if (MG_Util::ShaderTranspiler::ShaderCompiler::LowerRectImagesForEssl(*effectiveSpirv,
|
||||
rectLoweredSpirv) &&
|
||||
!rectLoweredSpirv.empty()) {
|
||||
effectiveSpirv = &rectLoweredSpirv;
|
||||
loweredRectImages = true;
|
||||
}
|
||||
|
||||
MG_Util::ShaderTranspiler::SpvcSession spvcSession(*effectiveSpirv,
|
||||
@@ -3401,6 +3432,26 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
source = ForceFlatIntegerVaryings(source, glShaderType);
|
||||
source = BroadcastLegacyFragColor(std::move(source), glShaderType, m_fragColorBroadcastCount);
|
||||
source = EmulateTextureLodBias(source);
|
||||
if (loweredRectImages) {
|
||||
// The image type is 2D now, so the transpiled lookups address [0,1]; the
|
||||
// application wrote them in texels. Only the frontend still knows which
|
||||
// samplers were declared rectangle.
|
||||
Vector<String> rectSamplerNames;
|
||||
const Uint uniformCount = stateProgramObject->GetUniformCount();
|
||||
for (Uint i = 0; i < uniformCount; ++i) {
|
||||
switch (stateProgramObject->GetActiveUniformType(i)) {
|
||||
case GL_SAMPLER_2D_RECT:
|
||||
case GL_SAMPLER_2D_RECT_SHADOW:
|
||||
case GL_INT_SAMPLER_2D_RECT:
|
||||
case GL_UNSIGNED_INT_SAMPLER_2D_RECT:
|
||||
rectSamplerNames.push_back(stateProgramObject->GetActiveUniformName(i));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
source = NormalizeRectSamplerCoordinates(source, rectSamplerNames);
|
||||
}
|
||||
source = EmulateBaseInstanceInVertexShader(std::move(source), glShaderType);
|
||||
source = PromoteDrawParameterGlobalsToUniforms(std::move(source), glShaderType);
|
||||
source = ForceSupporterOutput(source);
|
||||
|
||||
@@ -596,6 +596,67 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
String NormalizeRectSamplerCoordinates(const String& glslCode,
|
||||
const Vector<String>& rectSamplerNames) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (rectSamplerNames.empty() || glslCode.find("texture") == String::npos) {
|
||||
return glslCode;
|
||||
}
|
||||
|
||||
// Lookups whose argument 1 is a plain (non-projective) texel-space coordinate on a
|
||||
// rectangle sampler. texelFetch* is absent on purpose: its coordinates are integer
|
||||
// texels on the 2D target too, so it already lands in the right place.
|
||||
static const char* const kRectCoordinateLookups[] = {
|
||||
"textureGatherOffsets", "textureGatherOffset", "textureGather",
|
||||
"textureOffset", "texture",
|
||||
};
|
||||
|
||||
String result = glslCode;
|
||||
// Right to left, so the offsets of the not-yet-rewritten calls stay valid.
|
||||
for (SizeT scan = result.size(); scan-- > 0;) {
|
||||
if (result[scan] != 't') continue;
|
||||
if (scan > 0 && IsIdentifierChar(result[scan - 1])) continue;
|
||||
|
||||
SizeT openParen = 0;
|
||||
Bool matched = false;
|
||||
for (const char* name : kRectCoordinateLookups) {
|
||||
const SizeT nameLength = std::strlen(name);
|
||||
if (result.compare(scan, nameLength, name) != 0) continue;
|
||||
const SizeT after = result.find_first_not_of(" \t", scan + nameLength);
|
||||
if (after == String::npos || result[after] != '(') continue;
|
||||
openParen = after;
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
if (!matched) continue;
|
||||
|
||||
const Vector<SizeT> marks = SplitCallArguments(result, openParen);
|
||||
if (marks.size() < 2) continue; // needs a sampler and a coordinate
|
||||
|
||||
const SizeT firstArgStart = result.find_first_not_of(" \t", openParen + 1);
|
||||
SizeT firstArgEnd = marks.front();
|
||||
while (firstArgEnd > firstArgStart &&
|
||||
(result[firstArgEnd - 1] == ' ' || result[firstArgEnd - 1] == '\t')) {
|
||||
--firstArgEnd;
|
||||
}
|
||||
if (firstArgStart == String::npos || firstArgEnd <= firstArgStart) continue;
|
||||
const String samplerName = result.substr(firstArgStart, firstArgEnd - firstArgStart);
|
||||
if (std::find(rectSamplerNames.begin(), rectSamplerNames.end(), samplerName) ==
|
||||
rectSamplerNames.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Wrap argument 1: (coord) / vec2(textureSize(sampler, 0)).
|
||||
const SizeT coordStart = marks[0] + 1;
|
||||
const SizeT coordEnd = marks[1];
|
||||
result.insert(coordEnd, String(") / vec2(textureSize(") + samplerName + ", 0)))");
|
||||
result.insert(coordStart, "((");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // namespace PrgramImpl
|
||||
|
||||
namespace Utils {
|
||||
|
||||
@@ -129,6 +129,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// all have a zero bias is therefore unaffected. Returns the source unchanged when
|
||||
// there is nothing to rewrite.
|
||||
String EmulateTextureLodBias(const String& glslCode);
|
||||
// GL_TEXTURE_RECTANGLE is emulated on an ES 2D texture and LowerRectImagesForEssl
|
||||
// rewrites the image type to match, but a rectangle lookup addresses texels
|
||||
// directly while a 2D one addresses [0,1] - so every lookup that takes normalized
|
||||
// coordinates has to divide by the texture's size. `rectSamplerNames` is the set of
|
||||
// samplers the program declared as rectangle; texelFetch is left alone (its
|
||||
// coordinates are unnormalized on both targets) and so is anything projective,
|
||||
// which LowerRectImagesForEssl still declines outright.
|
||||
String NormalizeRectSamplerCoordinates(const String& glslCode,
|
||||
const Vector<String>& rectSamplerNames);
|
||||
} // namespace PrgramImpl
|
||||
|
||||
namespace Utils {
|
||||
|
||||
@@ -1578,6 +1578,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// (and, via the SharedPtrs, the records), never pool slots, so
|
||||
// stale queries are always safe to delete.
|
||||
Uint64 rendererGeneration = 0;
|
||||
// Kind::XfbGenerated - the frontend's paused-draw primitive counter when the
|
||||
// query began. VK_QUERY_TYPE_TRANSFORM_FEEDBACK_STREAM_EXT counts only what the
|
||||
// capture saw, so a draw made while the span was paused is invisible to it -
|
||||
// but GL_PRIMITIVES_GENERATED counts what the last vertex processing stage
|
||||
// emitted regardless. The delta closes that gap at result time.
|
||||
Uint64 pausedPrimitiveSnapshot = 0;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
@@ -1676,6 +1682,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
primitives)) {
|
||||
return false;
|
||||
}
|
||||
if (query->kind == VulkanTimerQuery::Kind::XfbGenerated && MG_State::pGLContext != nullptr) {
|
||||
primitives += MG_State::pGLContext->GetTransformFeedbackPausedPrimitiveCounter() -
|
||||
query->pausedPrimitiveSnapshot;
|
||||
}
|
||||
*outNanoseconds = primitives;
|
||||
return true;
|
||||
}
|
||||
@@ -1719,6 +1729,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
auto* query = new VulkanTimerQuery{};
|
||||
query->kind = generated ? VulkanTimerQuery::Kind::XfbGenerated : VulkanTimerQuery::Kind::XfbWritten;
|
||||
query->rendererGeneration = GetRendererGeneration();
|
||||
query->pausedPrimitiveSnapshot =
|
||||
MG_State::pGLContext ? MG_State::pGLContext->GetTransformFeedbackPausedPrimitiveCounter() : 0;
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
@@ -650,6 +650,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The shader may write this buffer, and those writes land in GPU memory behind the
|
||||
// frontend's CPU shadow - which is what MapBuffer and GetBufferSubData read.
|
||||
// Host-visible coherent GPU residency makes the shadow BE that memory, so the
|
||||
// results are visible without a readback path, exactly as for a capture buffer.
|
||||
bufferObject->EnsureGpuResidentStorage();
|
||||
// ... and the read that follows has to wait for this draw or dispatch to retire.
|
||||
bufferObject->MarkGpuWritten();
|
||||
|
||||
BufferSlice slice{};
|
||||
if (!m_bufferManager->AcquireResidentSlice(BufferKind::ShaderStorage, bufferObject, slice) || !slice.IsValid()) {
|
||||
MGLOG_E("ResolveStorageBufferDescriptor: failed to sync GL buffer %u for block '%s'",
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
// End of Source File Header
|
||||
|
||||
#include "VkBufferManager.h"
|
||||
#include "../DirectVulkan.h"
|
||||
#include "VulkanRenderer.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
namespace {
|
||||
@@ -57,6 +59,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
}
|
||||
|
||||
// The CPU is about to read a buffer a shader wrote. Its bytes live in coherent
|
||||
// host-visible GPU storage (EnsureGpuResidentStorage adopts it when the buffer is
|
||||
// bound as a shader storage buffer), so nothing needs copying - but coherence only
|
||||
// says the writes are visible once they have happened, so the work has to retire
|
||||
// first.
|
||||
void Ops_ReadbackFromGpu(BufferObject& bufferObject) {
|
||||
(void)bufferObject;
|
||||
if (pVulkanRenderer) {
|
||||
pVulkanRenderer->FinishPendingGpuWork();
|
||||
}
|
||||
}
|
||||
|
||||
void* Ops_AcquirePersistentMap(BufferObject& bufferObject) {
|
||||
if (g_activeBufferManager) {
|
||||
return g_activeBufferManager->AcquirePersistentMap(bufferObject);
|
||||
@@ -80,6 +94,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
.FlushMappedRange = Ops_FlushMappedRange,
|
||||
.OnDestroy = Ops_OnDestroy,
|
||||
.AcquirePersistentMap = Ops_AcquirePersistentMap,
|
||||
.ReadbackFromGpu = Ops_ReadbackFromGpu,
|
||||
};
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -2798,6 +2798,10 @@ void main() {
|
||||
}
|
||||
m_vertexInputStateFactory.reset();
|
||||
m_xfbCounterBuffer.Destroy();
|
||||
m_xfbCounterSlotByObject.clear();
|
||||
m_xfbNextCounterSlot = 0;
|
||||
m_xfbCountersValid.fill(false);
|
||||
m_xfbLastSeenGeneration.fill(0);
|
||||
if (m_occlusionQueryPool != VK_NULL_HANDLE) {
|
||||
vkDestroyQueryPool(m_device, m_occlusionQueryPool, nullptr);
|
||||
m_occlusionQueryPool = VK_NULL_HANDLE;
|
||||
@@ -6915,6 +6919,17 @@ void main() {
|
||||
|
||||
}
|
||||
|
||||
Bool VulkanRenderer::FinishPendingGpuWork() {
|
||||
auto& frame = m_frameContext.GetCurrent();
|
||||
if (!frame.isCommandRecording) {
|
||||
return true;
|
||||
}
|
||||
if (VkRenderPassManager::GetActiveRenderPass() != nullptr) {
|
||||
VkRenderPassManager::EndRenderPass(frame.commandBuffer);
|
||||
}
|
||||
return SubmitReadbackCommandsAndWait(frame);
|
||||
}
|
||||
|
||||
Bool VulkanRenderer::SubmitReadbackCommandsAndWait(FrameContext::FrameData& frame) {
|
||||
if (frame.isCommandRecording) {
|
||||
m_frameContext.EndCommandRecording();
|
||||
@@ -8000,11 +8015,30 @@ void main() {
|
||||
resource->layout = finalLayout;
|
||||
}
|
||||
|
||||
Uint32 VulkanRenderer::CurrentXfbCounterSlot() {
|
||||
const Uint name = MG_State::pGLContext->GetBoundTransformFeedbackName();
|
||||
const auto it = m_xfbCounterSlotByObject.find(name);
|
||||
if (it != m_xfbCounterSlotByObject.end()) {
|
||||
return it->second;
|
||||
}
|
||||
// Past the tracked set every object shares slot group 0. Only concurrently-paused
|
||||
// spans need distinct groups, and applications do not keep sixteen of those open.
|
||||
const Uint32 slot = m_xfbNextCounterSlot < kXfbCounterObjectSlots ? m_xfbNextCounterSlot++ : 0;
|
||||
m_xfbCounterSlotByObject[name] = slot;
|
||||
return slot;
|
||||
}
|
||||
|
||||
Bool VulkanRenderer::BeginXfbCaptureForDraw(FrameContext::FrameData& frame) {
|
||||
if (!m_transformFeedbackFeatureEnabled || MG_State::pGLContext == nullptr ||
|
||||
!MG_State::pGLContext->IsTransformFeedbackActive()) {
|
||||
return false;
|
||||
}
|
||||
// A paused span captures nothing, and the counter buffers keep their values, so the
|
||||
// next resumed draw appends exactly where the last captured one stopped - which is
|
||||
// what pause/resume means (ARB_transform_feedback2).
|
||||
if (MG_State::pGLContext->IsTransformFeedbackPaused()) {
|
||||
return false;
|
||||
}
|
||||
const auto& program = MG_State::pGLContext->GetTransformFeedbackProgram();
|
||||
if (!program || program->GetTransformFeedbackVaryingCount() == 0) {
|
||||
return false;
|
||||
@@ -8017,7 +8051,7 @@ void main() {
|
||||
if (!m_xfbCounterBuffer.IsValid()) {
|
||||
if (!m_xfbCounterBuffer.Create({
|
||||
.allocator = m_allocator,
|
||||
.size = 16,
|
||||
.size = 16 * kXfbCounterObjectSlots,
|
||||
.usage = VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT |
|
||||
VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
.memoryUsage = VMA_MEMORY_USAGE_AUTO,
|
||||
@@ -8058,15 +8092,16 @@ void main() {
|
||||
s_vkCmdBindTransformFeedbackBuffersEXT(frame.commandBuffer, 0, static_cast<Uint32>(bufferCount), buffers,
|
||||
offsets, sizes);
|
||||
|
||||
const Uint32 counterSlot = CurrentXfbCounterSlot();
|
||||
const Uint64 generation = MG_State::pGLContext->GetTransformFeedbackGeneration();
|
||||
const Bool resume = m_xfbCountersValid && m_xfbLastSeenGeneration == generation;
|
||||
m_xfbLastSeenGeneration = generation;
|
||||
const Bool resume = m_xfbCountersValid[counterSlot] && m_xfbLastSeenGeneration[counterSlot] == generation;
|
||||
m_xfbLastSeenGeneration[counterSlot] = 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;
|
||||
counterOffsets[i] = static_cast<VkDeviceSize>(counterSlot) * 16 + static_cast<VkDeviceSize>(i) * 4;
|
||||
}
|
||||
if (resume) {
|
||||
s_vkCmdBeginTransformFeedbackEXT(frame.commandBuffer, 0, static_cast<Uint32>(bufferCount),
|
||||
@@ -8083,15 +8118,16 @@ void main() {
|
||||
}
|
||||
const auto& program = MG_State::pGLContext->GetTransformFeedbackProgram();
|
||||
const SizeT bufferCount = program ? std::min<SizeT>(program->GetTransformFeedbackBufferCount(), 4) : 0;
|
||||
const Uint32 counterSlot = CurrentXfbCounterSlot();
|
||||
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;
|
||||
counterOffsets[i] = static_cast<VkDeviceSize>(counterSlot) * 16 + static_cast<VkDeviceSize>(i) * 4;
|
||||
}
|
||||
s_vkCmdEndTransformFeedbackEXT(frame.commandBuffer, 0, static_cast<Uint32>(bufferCount), counterBuffers,
|
||||
counterOffsets);
|
||||
m_xfbCountersValid = true;
|
||||
m_xfbCountersValid[counterSlot] = true;
|
||||
}
|
||||
|
||||
void VulkanRenderer::DrawArrays(const DrawCmd& payload) {
|
||||
@@ -8380,6 +8416,16 @@ void main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Byte size of the command structures GL defines for the indirect draws (GL 4.6 core
|
||||
// 10.3.10): four uint32 for DrawArraysIndirectCommand, five for DrawElementsIndirect-
|
||||
// Command. These bound the read out of GL_DRAW_INDIRECT_BUFFER and are the default
|
||||
// stride, so they must be GL's sizes and not this renderer's own draw-parameter
|
||||
// structs - DrawCmdParam carries two extra members and is 24 bytes, which made every
|
||||
// glDrawArraysIndirect on a tightly-sized indirect buffer look out of range and draw
|
||||
// nothing.
|
||||
constexpr SizeT kGLDrawArraysIndirectCommandBytes = 4 * sizeof(Uint32);
|
||||
constexpr SizeT kGLDrawElementsIndirectCommandBytes = 5 * sizeof(Uint32);
|
||||
|
||||
void VulkanRenderer::MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect,
|
||||
GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride) {
|
||||
auto& frame = m_frameContext.GetCurrent();
|
||||
@@ -8388,11 +8434,11 @@ void main() {
|
||||
return;
|
||||
}
|
||||
if (stride == 0) {
|
||||
stride = sizeof(DrawIndexedCmdParam);
|
||||
stride = kGLDrawElementsIndirectCommandBytes;
|
||||
}
|
||||
if (stride < static_cast<GLsizei>(sizeof(DrawIndexedCmdParam))) {
|
||||
if (stride < static_cast<GLsizei>(kGLDrawElementsIndirectCommandBytes)) {
|
||||
MGLOG_E("MultiDrawElementsIndirectCount skipped: stride %d is smaller than command size %zu",
|
||||
stride, sizeof(DrawIndexedCmdParam));
|
||||
stride, kGLDrawElementsIndirectCommandBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8411,7 +8457,7 @@ void main() {
|
||||
|
||||
const SizeT commandOffset = reinterpret_cast<SizeT>(indirect);
|
||||
const SizeT commandBytes = commandOffset +
|
||||
static_cast<SizeT>(stride) * static_cast<SizeT>(maxdrawcount - 1) + sizeof(DrawIndexedCmdParam);
|
||||
static_cast<SizeT>(stride) * static_cast<SizeT>(maxdrawcount - 1) + kGLDrawElementsIndirectCommandBytes;
|
||||
auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
|
||||
if (!drawBuffer || commandBytes > drawBuffer->GetSize()) {
|
||||
MGLOG_E("MultiDrawElementsIndirectCount skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range");
|
||||
@@ -8489,11 +8535,11 @@ void main() {
|
||||
return;
|
||||
}
|
||||
if (stride == 0) {
|
||||
stride = sizeof(DrawIndexedCmdParam);
|
||||
stride = kGLDrawElementsIndirectCommandBytes;
|
||||
}
|
||||
if (stride < static_cast<GLsizei>(sizeof(DrawIndexedCmdParam))) {
|
||||
if (stride < static_cast<GLsizei>(kGLDrawElementsIndirectCommandBytes)) {
|
||||
MGLOG_E("MultiDrawElementsIndirect skipped: stride %d is smaller than command size %zu",
|
||||
stride, sizeof(DrawIndexedCmdParam));
|
||||
stride, kGLDrawElementsIndirectCommandBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8512,7 +8558,7 @@ void main() {
|
||||
|
||||
const SizeT commandOffset = reinterpret_cast<SizeT>(indirect);
|
||||
const SizeT commandBytes = commandOffset +
|
||||
static_cast<SizeT>(stride) * static_cast<SizeT>(drawcount - 1) + sizeof(DrawIndexedCmdParam);
|
||||
static_cast<SizeT>(stride) * static_cast<SizeT>(drawcount - 1) + kGLDrawElementsIndirectCommandBytes;
|
||||
auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
|
||||
if (!drawBuffer || commandBytes > drawBuffer->GetSize()) {
|
||||
MGLOG_E("MultiDrawElementsIndirect skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range");
|
||||
@@ -8571,17 +8617,17 @@ void main() {
|
||||
return;
|
||||
}
|
||||
if (stride == 0) {
|
||||
stride = sizeof(DrawCmdParam);
|
||||
stride = kGLDrawArraysIndirectCommandBytes;
|
||||
}
|
||||
if (stride < static_cast<GLsizei>(sizeof(DrawCmdParam))) {
|
||||
if (stride < static_cast<GLsizei>(kGLDrawArraysIndirectCommandBytes)) {
|
||||
MGLOG_E("MultiDrawArraysIndirect skipped: stride %d is smaller than command size %zu",
|
||||
stride, sizeof(DrawCmdParam));
|
||||
stride, kGLDrawArraysIndirectCommandBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
const SizeT commandOffset = reinterpret_cast<SizeT>(indirect);
|
||||
const SizeT commandBytes = commandOffset +
|
||||
static_cast<SizeT>(stride) * static_cast<SizeT>(drawcount - 1) + sizeof(DrawCmdParam);
|
||||
static_cast<SizeT>(stride) * static_cast<SizeT>(drawcount - 1) + kGLDrawArraysIndirectCommandBytes;
|
||||
auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
|
||||
if (!drawBuffer || commandBytes > drawBuffer->GetSize()) {
|
||||
MGLOG_E("MultiDrawArraysIndirect skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range");
|
||||
|
||||
@@ -498,12 +498,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
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.
|
||||
// draws within one glBeginTransformFeedback append GL-style. Transform feedback
|
||||
// objects can each hold an open, paused span at the same time, so the counters are
|
||||
// per object: one group of four slots each, handed out on first use.
|
||||
static constexpr SizeT kXfbCounterObjectSlots = 16;
|
||||
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;
|
||||
UnorderedMap<Uint, Uint32> m_xfbCounterSlotByObject;
|
||||
Uint32 m_xfbNextCounterSlot = 0;
|
||||
// Set for a slot once a captured draw has been recorded into its span; selects
|
||||
// counter-buffer resume on the next captured draw of the same span.
|
||||
Array<Bool, kXfbCounterObjectSlots> m_xfbCountersValid{};
|
||||
Array<Uint64, kXfbCounterObjectSlots> m_xfbLastSeenGeneration{};
|
||||
// Counter slot group of the bound transform feedback object.
|
||||
Uint32 CurrentXfbCounterSlot();
|
||||
// Wraps a recorded draw with BeginTransformFeedbackEXT/EndTransformFeedbackEXT
|
||||
// when GL transform feedback is active; binds capture buffers on demand.
|
||||
Bool BeginXfbCaptureForDraw(FrameContext::FrameData& frame);
|
||||
@@ -788,6 +795,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkImageLayout finalLayout);
|
||||
Bool SubmitReadbackCommandsAndWait(FrameContext::FrameData& frame);
|
||||
|
||||
public:
|
||||
// Submits whatever is recorded and waits for it. The CPU is about to read memory
|
||||
// a shader wrote (a mapped shader storage buffer), and coherent host-visible
|
||||
// storage only guarantees visibility once the work that produced it has retired.
|
||||
Bool FinishPendingGpuWork();
|
||||
|
||||
private:
|
||||
|
||||
void ShutdownSwapchain();
|
||||
|
||||
// Static functions
|
||||
|
||||
@@ -330,12 +330,21 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
} else if (access & BufferMappingAccessBit::Write) {
|
||||
*params = GL_WRITE_ONLY;
|
||||
} else {
|
||||
*params = 0;
|
||||
*params = GL_READ_WRITE;
|
||||
}
|
||||
} else {
|
||||
*params = 0;
|
||||
// Initial value, and what glUnmapBuffer restores (GL 4.6 core table 6.2).
|
||||
*params = GL_READ_WRITE;
|
||||
}
|
||||
break;
|
||||
case GL_BUFFER_ACCESS_FLAGS:
|
||||
// The MapBufferRange flags verbatim; glMapBuffer's access enum has already been
|
||||
// normalised into the same bits. Zero while the buffer is not mapped.
|
||||
*params = bufferObject->IsMapped()
|
||||
? static_cast<GLint>(
|
||||
MG_Util::ConvertBufferMappingAccessToGLEnum(bufferObject->GetMappingAccess()))
|
||||
: 0;
|
||||
break;
|
||||
case GL_BUFFER_MAPPED:
|
||||
*params = bufferObject->IsMapped() ? GL_TRUE : GL_FALSE;
|
||||
break;
|
||||
@@ -878,6 +887,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return;
|
||||
}
|
||||
|
||||
bufferObject->SyncGpuWrites();
|
||||
bufferObject->DownloadSubData(data, static_cast<SizeT>(offset), static_cast<SizeT>(size));
|
||||
}
|
||||
|
||||
@@ -1366,6 +1376,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
if (buffer == 0) {
|
||||
point.Bind(nullptr);
|
||||
point.SetRange(Range1D(0, 0));
|
||||
GetBufferBindingSlot(bufferTarget).Bind(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1384,6 +1395,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
} else {
|
||||
point.ClearRange();
|
||||
}
|
||||
// The indexed bind also binds to the generic binding point of the same target
|
||||
// (GL 4.6 core 6.1.1). Callers rely on it: the texture_gather tests set up their
|
||||
// SSBO with BindBufferBase and then size it through glBufferData on the generic
|
||||
// target alone, which would otherwise raise GL_INVALID_OPERATION and leave the
|
||||
// buffer with no storage.
|
||||
GetBufferBindingSlot(bufferTarget).Bind(bufferObject);
|
||||
}
|
||||
|
||||
void BindBufferRange_State(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) {
|
||||
@@ -1407,6 +1424,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
if (buffer == 0) {
|
||||
point.Bind(nullptr);
|
||||
point.SetRange(Range1D(0, 0));
|
||||
GetBufferBindingSlot(bufferTarget).Bind(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1424,6 +1442,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
} else {
|
||||
point.ClearRange();
|
||||
}
|
||||
// Also the generic binding point, exactly as BindBufferBase (GL 4.6 core 6.1.1).
|
||||
GetBufferBindingSlot(bufferTarget).Bind(bufferObject);
|
||||
}
|
||||
|
||||
/* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_State/EGLState/Core.h>
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include "../Getter/GL_Getter.h"
|
||||
|
||||
namespace MobileGL::MG_Impl::GLImpl {
|
||||
static Bool ValidateCurrentProgramForExecution(const char* functionName) {
|
||||
@@ -72,6 +73,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
// Geometry amplification is not modelled here.
|
||||
static void AccountTransformFeedbackPrimitives(GLenum mode, GLsizei count) {
|
||||
if (!MG_State::pGLContext->IsTransformFeedbackActive()) return;
|
||||
// A paused span captures nothing, so a draw made while paused contributes to
|
||||
// PRIMITIVES_GENERATED but not to TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN.
|
||||
if (MG_State::pGLContext->IsTransformFeedbackPaused()) {
|
||||
MG_State::pGLContext->AddTransformFeedbackPausedPrimitives(CountPrimitivesForDraw(mode, count));
|
||||
return;
|
||||
}
|
||||
Uint64 primitives = CountPrimitivesForDraw(mode, count);
|
||||
if (primitives == 0) return;
|
||||
MG_State::pGLContext->AddTransformFeedbackInputPrimitives(primitives);
|
||||
@@ -115,7 +122,36 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
MG_State::pGLContext->AddTransformFeedbackCapturedVertices(primitives * verticesPerPrimitive);
|
||||
}
|
||||
|
||||
// Every primitive mode a draw command accepts (GL 4.6 core table 10.1, plus
|
||||
// GL_PATCHES for the tessellation pipeline). Anything else is GL_INVALID_ENUM.
|
||||
static Bool IsAcceptedPrimitiveMode(GLenum mode) {
|
||||
switch (mode) {
|
||||
case GL_POINTS:
|
||||
case GL_LINES:
|
||||
case GL_LINE_LOOP:
|
||||
case GL_LINE_STRIP:
|
||||
case GL_LINES_ADJACENCY:
|
||||
case GL_LINE_STRIP_ADJACENCY:
|
||||
case GL_TRIANGLES:
|
||||
case GL_TRIANGLE_STRIP:
|
||||
case GL_TRIANGLE_FAN:
|
||||
case GL_TRIANGLES_ADJACENCY:
|
||||
case GL_TRIANGLE_STRIP_ADJACENCY:
|
||||
case GL_PATCHES:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Bool ValidatePrimitiveModeForBackend(const char* functionName, GLenum mode) {
|
||||
if (!IsAcceptedPrimitiveMode(mode)) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName, "mode is not an accepted primitive type."));
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& activeBackendObject = MG_Backend::pActiveBackendObject;
|
||||
if (!activeBackendObject) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
@@ -133,11 +169,51 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return false;
|
||||
}
|
||||
|
||||
// A geometry stage only accepts the primitive types that decompose into its declared
|
||||
// input primitive (GL 4.6 core 11.3.1); anything else is INVALID_OPERATION. GL_PATCHES
|
||||
// is the tessellation pipeline's input and reaches the geometry stage already
|
||||
// converted, so it is not constrained here.
|
||||
const auto& currentProgram = MG_State::pGLContext->GetCurrentProgram();
|
||||
const GLenum gsInput = currentProgram ? currentProgram->GetGeometryInputType() : GL_NONE;
|
||||
if (gsInput != GL_NONE && mode != GL_PATCHES) {
|
||||
Bool compatible = false;
|
||||
switch (gsInput) {
|
||||
case GL_POINTS:
|
||||
compatible = mode == GL_POINTS;
|
||||
break;
|
||||
case GL_LINES:
|
||||
compatible = mode == GL_LINES || mode == GL_LINE_STRIP || mode == GL_LINE_LOOP;
|
||||
break;
|
||||
case GL_LINES_ADJACENCY:
|
||||
compatible = mode == GL_LINES_ADJACENCY || mode == GL_LINE_STRIP_ADJACENCY;
|
||||
break;
|
||||
case GL_TRIANGLES:
|
||||
compatible = mode == GL_TRIANGLES || mode == GL_TRIANGLE_STRIP || mode == GL_TRIANGLE_FAN;
|
||||
break;
|
||||
case GL_TRIANGLES_ADJACENCY:
|
||||
compatible = mode == GL_TRIANGLES_ADJACENCY || mode == GL_TRIANGLE_STRIP_ADJACENCY;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!compatible) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl", functionName,
|
||||
"Primitive mode is incompatible with the geometry shader's input primitive type."));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// While transform feedback is active the draw's primitive type must match
|
||||
// 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.
|
||||
// the draw mode itself is unconstrained here. A paused span is exempt: it
|
||||
// captures nothing, so there is nothing for the mode to be incompatible with
|
||||
// (GL 4.6 core 13.2.3).
|
||||
if (MG_State::pGLContext->IsTransformFeedbackActive() &&
|
||||
!MG_State::pGLContext->IsTransformFeedbackPaused() &&
|
||||
!(MG_State::pGLContext->GetTransformFeedbackProgram() &&
|
||||
MG_State::pGLContext->GetTransformFeedbackProgram()->GetShaderIndexByStage(ShaderStage::Geometry) >= 0)) {
|
||||
const GLenum feedbackMode = MG_State::pGLContext->GetTransformFeedbackPrimitiveMode();
|
||||
@@ -168,6 +244,58 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Byte size of the command structures the indirect draws read (GL 4.6 core 10.3.10).
|
||||
constexpr SizeT kDrawArraysIndirectCommandBytes = 4 * sizeof(Uint32);
|
||||
constexpr SizeT kDrawElementsIndirectCommandBytes = 5 * sizeof(Uint32);
|
||||
|
||||
// Shared preconditions of every *Indirect draw: `indirect` is a byte offset into the
|
||||
// buffer bound to GL_DRAW_INDIRECT_BUFFER, must be 4-byte aligned, and the whole
|
||||
// command has to lie inside that buffer.
|
||||
static Bool ValidateIndirectDrawSource(const char* functionName, const void* indirect, SizeT commandBytes) {
|
||||
const auto offset = reinterpret_cast<uintptr_t>(indirect);
|
||||
if (offset % 4 != 0) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||
"indirect offset must be a multiple of 4."));
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& buffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
|
||||
if (!buffer) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||
"No buffer is bound to GL_DRAW_INDIRECT_BUFFER."));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (offset + commandBytes > buffer->GetSize()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||
"The indirect command extends past the end of the bound "
|
||||
"GL_DRAW_INDIRECT_BUFFER."));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Index type accepted by the DrawElements family (GL 4.6 core 10.3.9).
|
||||
static Bool ValidateDrawElementsIndexType(const char* functionName, GLenum type) {
|
||||
switch (type) {
|
||||
case GL_UNSIGNED_BYTE:
|
||||
case GL_UNSIGNED_SHORT:
|
||||
case GL_UNSIGNED_INT:
|
||||
return true;
|
||||
default:
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName, "type is not an accepted index type."));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Clear_Backend(GLbitfield mask) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
@@ -362,6 +490,28 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
dispatchComputeIndirect(indirect);
|
||||
}
|
||||
|
||||
void PatchParameteri(GLenum pname, GLint value) {
|
||||
if (pname != GL_PATCH_VERTICES) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "pname must be GL_PATCH_VERTICES."));
|
||||
return;
|
||||
}
|
||||
GLint maxPatchVertices = 32;
|
||||
GetIntegerv(GL_MAX_PATCH_VERTICES, &maxPatchVertices);
|
||||
if (value <= 0 || value > maxPatchVertices) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"value must be in [1, GL_MAX_PATCH_VERTICES]."));
|
||||
return;
|
||||
}
|
||||
MG_State::pGLContext->SetPatchVertices(static_cast<Uint>(value));
|
||||
if (const auto patchParameteri = MG_Backend::gBackendFunctionsTable.GL.PatchParameteri) {
|
||||
patchParameteri(pname, value);
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryBarrier(GLbitfield barriers) {
|
||||
auto memoryBarrier = MG_Backend::gBackendFunctionsTable.GL.MemoryBarrier;
|
||||
if (!memoryBarrier) {
|
||||
@@ -467,6 +617,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect) {
|
||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||
if (!ValidateDrawElementsIndexType(__func__, type)) return;
|
||||
if (!ValidateIndirectDrawSource(__func__, indirect, kDrawElementsIndirectCommandBytes)) return;
|
||||
DrawElementsIndirect_Backend(mode, type, indirect);
|
||||
}
|
||||
|
||||
@@ -486,6 +638,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
void DrawArraysIndirect(GLenum mode, const void* indirect) {
|
||||
if (!ValidateCurrentProgramForExecution(__func__)) return;
|
||||
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
|
||||
if (!ValidateIndirectDrawSource(__func__, indirect, kDrawArraysIndirectCommandBytes)) return;
|
||||
DrawArraysIndirect_Backend(mode, indirect);
|
||||
}
|
||||
|
||||
@@ -563,9 +716,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
"No program with transform feedback varyings is active."));
|
||||
return;
|
||||
}
|
||||
// Every capture buffer slot the program's mode uses must have a buffer bound.
|
||||
// Every capture buffer slot the program's mode uses must have a buffer bound. A slot
|
||||
// of stride 0 - two consecutive gl_NextBuffer entries - captures nothing and so needs
|
||||
// no binding.
|
||||
const SizeT usedBufferCount = program->GetTransformFeedbackBufferCount();
|
||||
for (SizeT i = 0; i < usedBufferCount; ++i) {
|
||||
if (program->GetTransformFeedbackStride(static_cast<Uint32>(i)) == 0) continue;
|
||||
const auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback,
|
||||
static_cast<Uint>(i));
|
||||
if (point.GetBoundObject() == nullptr) {
|
||||
@@ -679,4 +835,175 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
FixupGsStripCaptureOrder(capturedProgram, inputPrimitives);
|
||||
}
|
||||
|
||||
void PauseTransformFeedback(void) {
|
||||
if (!MG_State::pGLContext->IsTransformFeedbackActive() ||
|
||||
MG_State::pGLContext->IsTransformFeedbackPaused()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"Transform feedback is not active, or is already paused."));
|
||||
return;
|
||||
}
|
||||
MG_State::pGLContext->SetTransformFeedbackPaused(true);
|
||||
if (const auto pauseXfb = MG_Backend::gBackendFunctionsTable.GL.PauseTransformFeedback) {
|
||||
pauseXfb();
|
||||
}
|
||||
}
|
||||
|
||||
void ResumeTransformFeedback(void) {
|
||||
if (!MG_State::pGLContext->IsTransformFeedbackActive() ||
|
||||
!MG_State::pGLContext->IsTransformFeedbackPaused()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Transform feedback is not paused."));
|
||||
return;
|
||||
}
|
||||
MG_State::pGLContext->SetTransformFeedbackPaused(false);
|
||||
if (const auto resumeXfb = MG_Backend::gBackendFunctionsTable.GL.ResumeTransformFeedback) {
|
||||
resumeXfb();
|
||||
}
|
||||
}
|
||||
|
||||
void GenTransformFeedbacks(GLsizei n, GLuint* ids) {
|
||||
if (n < 0) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "n must be non-negative."));
|
||||
return;
|
||||
}
|
||||
if (n == 0 || ids == nullptr) return;
|
||||
Vector<Uint> names;
|
||||
MG_State::pGLContext->GenTransformFeedbackNames(static_cast<Uint>(n), names);
|
||||
Memcpy(ids, names.data(), static_cast<SizeT>(n) * sizeof(GLuint));
|
||||
}
|
||||
|
||||
void DeleteTransformFeedbacks(GLsizei n, const GLuint* ids) {
|
||||
if (n < 0) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "n must be non-negative."));
|
||||
return;
|
||||
}
|
||||
if (ids == nullptr) return;
|
||||
for (GLsizei i = 0; i < n; ++i) {
|
||||
const GLuint id = ids[i];
|
||||
// Unknown names and 0 are silently ignored; an object whose capture span is
|
||||
// still open is not (GL 4.6 core 13.2.1).
|
||||
if (id == 0 || !MG_State::pGLContext->ValidateTransformFeedbackName(id)) continue;
|
||||
if (id == MG_State::pGLContext->GetBoundTransformFeedbackName() &&
|
||||
MG_State::pGLContext->IsTransformFeedbackActive()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"Cannot delete a transform feedback object whose capture is active."));
|
||||
continue;
|
||||
}
|
||||
if (const auto deleteXfb = MG_Backend::gBackendFunctionsTable.GL.DeleteTransformFeedback) {
|
||||
deleteXfb(id);
|
||||
}
|
||||
MG_State::pGLContext->MarkTransformFeedbackObjectForDeletion(id);
|
||||
}
|
||||
}
|
||||
|
||||
void BindTransformFeedback(GLenum target, GLuint id) {
|
||||
if (target != GL_TRANSFORM_FEEDBACK) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "target must be GL_TRANSFORM_FEEDBACK."));
|
||||
return;
|
||||
}
|
||||
// A running capture pins its object; only a paused one may be swapped out.
|
||||
if (MG_State::pGLContext->IsTransformFeedbackActive() &&
|
||||
!MG_State::pGLContext->IsTransformFeedbackPaused()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"Transform feedback is active and not paused."));
|
||||
return;
|
||||
}
|
||||
if (!MG_State::pGLContext->ValidateTransformFeedbackName(id)) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
std::to_string(id) + " is not a transform feedback object name."));
|
||||
return;
|
||||
}
|
||||
MG_State::pGLContext->BindTransformFeedbackObject(id);
|
||||
if (const auto bindXfb = MG_Backend::gBackendFunctionsTable.GL.BindTransformFeedback) {
|
||||
bindXfb(id);
|
||||
}
|
||||
}
|
||||
|
||||
GLboolean IsTransformFeedback(GLuint id) {
|
||||
// Name 0 is the default object, and a name glGenTransformFeedbacks handed out only
|
||||
// becomes the name of an object once it has been bound.
|
||||
return MG_State::pGLContext->IsTransformFeedbackObject(id) ? GL_TRUE : GL_FALSE;
|
||||
}
|
||||
|
||||
// glDrawTransformFeedback[Stream][Instanced]: replays the vertices the named object
|
||||
// captured in its last completed span, as if by glDrawArraysInstanced with that count
|
||||
// (GL 4.6 core 10.3.7).
|
||||
static void DrawTransformFeedbackImpl(const char* functionName, GLenum mode, GLuint id, GLuint stream,
|
||||
GLsizei instancecount) {
|
||||
if (!ValidateCurrentProgramForExecution(functionName)) return;
|
||||
if (!ValidatePrimitiveModeForBackend(functionName, mode)) return;
|
||||
if (instancecount < 0) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName, "instancecount must be non-negative."));
|
||||
return;
|
||||
}
|
||||
if (!MG_State::pGLContext->ValidateTransformFeedbackName(id)) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||
std::to_string(id) + " is not a transform feedback object name."));
|
||||
return;
|
||||
}
|
||||
// GL_MAX_VERTEX_STREAMS is 1, so stream 0 is the only one that exists.
|
||||
if (stream != 0) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||
"stream must be less than GL_MAX_VERTEX_STREAMS."));
|
||||
return;
|
||||
}
|
||||
// Drawing from an object whose capture is currently open is legal and deliberate:
|
||||
// it is how a transform feedback result is fed straight back into the next span
|
||||
// (ARB_transform_feedback2 lists no such restriction).
|
||||
if (!MG_State::pGLContext->HasTransformFeedbackCompletedSpan(id)) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||
"glEndTransformFeedback has never been called for this object."));
|
||||
return;
|
||||
}
|
||||
|
||||
const Uint64 vertices = MG_State::pGLContext->GetTransformFeedbackRecordedVertices(id);
|
||||
if (vertices == 0) return;
|
||||
const auto count = static_cast<GLsizei>(vertices);
|
||||
AccountTransformFeedbackPrimitives(mode, count);
|
||||
if (instancecount == 1) {
|
||||
DrawArrays_Backend(mode, 0, count);
|
||||
} else {
|
||||
DrawArraysInstanced_Backend(mode, 0, count, instancecount);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawTransformFeedback(GLenum mode, GLuint id) {
|
||||
DrawTransformFeedbackImpl(__func__, mode, id, 0, 1);
|
||||
}
|
||||
|
||||
void DrawTransformFeedbackInstanced(GLenum mode, GLuint id, GLsizei instancecount) {
|
||||
DrawTransformFeedbackImpl(__func__, mode, id, 0, instancecount);
|
||||
}
|
||||
|
||||
void DrawTransformFeedbackStream(GLenum mode, GLuint id, GLuint stream) {
|
||||
DrawTransformFeedbackImpl(__func__, mode, id, stream, 1);
|
||||
}
|
||||
|
||||
void DrawTransformFeedbackStreamInstanced(GLenum mode, GLuint id, GLuint stream, GLsizei instancecount) {
|
||||
DrawTransformFeedbackImpl(__func__, mode, id, stream, instancecount);
|
||||
}
|
||||
|
||||
} // namespace MobileGL::MG_Impl::GLImpl
|
||||
|
||||
@@ -13,8 +13,19 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */
|
||||
void BeginTransformFeedback(GLenum primitiveMode);
|
||||
void EndTransformFeedback(void);
|
||||
void PauseTransformFeedback(void);
|
||||
void ResumeTransformFeedback(void);
|
||||
void GenTransformFeedbacks(GLsizei n, GLuint* ids);
|
||||
void DeleteTransformFeedbacks(GLsizei n, const GLuint* ids);
|
||||
void BindTransformFeedback(GLenum target, GLuint id);
|
||||
GLboolean IsTransformFeedback(GLuint id);
|
||||
void DrawTransformFeedback(GLenum mode, GLuint id);
|
||||
void DrawTransformFeedbackInstanced(GLenum mode, GLuint id, GLsizei instancecount);
|
||||
void DrawTransformFeedbackStream(GLenum mode, GLuint id, GLuint stream);
|
||||
void DrawTransformFeedbackStreamInstanced(GLenum mode, GLuint id, GLuint stream, GLsizei instancecount);
|
||||
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
||||
void DispatchComputeIndirect(GLintptr indirect);
|
||||
void PatchParameteri(GLenum pname, GLint value);
|
||||
void MemoryBarrier(GLbitfield barriers);
|
||||
void MemoryBarrierByRegion(GLbitfield barriers);
|
||||
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride);
|
||||
|
||||
@@ -292,21 +292,15 @@ DECLARE_GL_FUNCTION_HEAD(void, SamplerParameterfv, GLuint sampler, GLenum pname,
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetSamplerParameteriv, GLuint sampler, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetSamplerParameteriv, sampler, pname, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetSamplerParameterfv, GLuint sampler, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetSamplerParameterfv, sampler, pname, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribDivisor, GLuint index, GLuint divisor) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribDivisor, index, divisor)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindTransformFeedback, GLenum target, GLuint id) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindTransformFeedback, target, id)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteTransformFeedbacks, GLsizei n, const GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteTransformFeedbacks, n, ids)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GenTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GenTransformFeedbacks, n, ids)
|
||||
// Transform feedback objects are not implemented, so no name is ever a live object. The shared
|
||||
// stub returns (type)1, telling a probing caller that every id it invents already exists; GL_FALSE
|
||||
// is both truthful and what the spec requires for a name that was never generated.
|
||||
MOBILEGL_GL_API GLboolean glIsTransformFeedback(GLuint id) {
|
||||
MGLOG_W("Stub function: %s(...)", __FUNCTION__);
|
||||
return GL_FALSE;
|
||||
}
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PauseTransformFeedback) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PauseTransformFeedback)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ResumeTransformFeedback) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ResumeTransformFeedback)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetProgramBinary, GLuint program, GLsizei bufSize, GLsizei* length, GLenum* binaryFormat, void* binary) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetProgramBinary, program, bufSize, length, binaryFormat, binary)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramBinary, GLuint program, GLenum binaryFormat, const void* binary, GLsizei length) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramBinary, program, binaryFormat, binary, length)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramParameteri, GLuint program, GLenum pname, GLint value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramParameteri, program, pname, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, BindTransformFeedback, GLenum target, GLuint id) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindTransformFeedback, target, id)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, DeleteTransformFeedbacks, GLsizei n, const GLuint* ids) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DeleteTransformFeedbacks, n, ids)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GenTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GenTransformFeedbacks, n, ids)
|
||||
DECLARE_GL_FUNCTION_HEAD(GLboolean, IsTransformFeedback, GLuint id) DECLARE_GL_FUNCTION_END(GLboolean, IsTransformFeedback, id)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, PauseTransformFeedback) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PauseTransformFeedback)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ResumeTransformFeedback) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ResumeTransformFeedback)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetProgramBinary, GLuint program, GLsizei bufSize, GLsizei* length, GLenum* binaryFormat, void* binary) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetProgramBinary, program, bufSize, length, binaryFormat, binary)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramBinary, GLuint program, GLenum binaryFormat, const void* binary, GLsizei length) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramBinary, program, binaryFormat, binary, length)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramParameteri, GLuint program, GLenum pname, GLint value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramParameteri, program, pname, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, InvalidateFramebuffer, GLenum target, GLsizei numAttachments, const GLenum* attachments) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, InvalidateFramebuffer, target, numAttachments, attachments)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, InvalidateSubFramebuffer, GLenum target, GLsizei numAttachments, const GLenum* attachments, GLint x, GLint y, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, InvalidateSubFramebuffer, target, numAttachments, attachments, x, y, width, height)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, TexStorage2D, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexStorage2D, target, levels, internalformat, width, height)
|
||||
@@ -430,7 +424,7 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnUniformfv, GLuint program, GLint locatio
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnUniformiv, GLuint program, GLint location, GLsizei bufSize, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnUniformiv, program, location, bufSize, params)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnUniformuiv, GLuint program, GLint location, GLsizei bufSize, GLuint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnUniformuiv, program, location, bufSize, params)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, MinSampleShading, GLfloat value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MinSampleShading, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PatchParameteri, GLenum pname, GLint value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PatchParameteri, pname, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, PatchParameteri, GLenum pname, GLint value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PatchParameteri, pname, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, TexParameterIiv, GLenum target, GLenum pname, const GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexParameterIiv, target, pname, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, TexParameterIuiv, GLenum target, GLenum pname, const GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexParameterIuiv, target, pname, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetTexParameterIiv, GLenum target, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTexParameterIiv, target, pname, params)
|
||||
@@ -915,24 +909,24 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, ColorP4ui, GLenum type, GLuint color) DECLAR
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ColorP4uiv, GLenum type, const GLuint* color) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ColorP4uiv, type, color)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, SecondaryColorP3ui, GLenum type, GLuint color) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, SecondaryColorP3ui, type, color)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, SecondaryColorP3uiv, GLenum type, const GLuint* color) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, SecondaryColorP3uiv, type, color)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform1d, GLint location, GLdouble x) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform1d, location, x)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform2d, GLint location, GLdouble x, GLdouble y) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform2d, location, x, y)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform3d, GLint location, GLdouble x, GLdouble y, GLdouble z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform3d, location, x, y, z)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform4d, GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform4d, location, x, y, z, w)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform1dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform1dv, location, count, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform2dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform2dv, location, count, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform3dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform3dv, location, count, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, Uniform4dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Uniform4dv, location, count, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix2dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix2dv, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix3dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix3dv, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix4dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix4dv, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix2x3dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix2x3dv, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix2x4dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix2x4dv, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix3x2dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix3x2dv, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix3x4dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix3x4dv, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix4x2dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix4x2dv, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformMatrix4x3dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UniformMatrix4x3dv, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetUniformdv, GLuint program, GLint location, GLdouble* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetUniformdv, program, location, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, Uniform1d, GLint location, GLdouble x) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform1d, location, x)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, Uniform2d, GLint location, GLdouble x, GLdouble y) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform2d, location, x, y)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, Uniform3d, GLint location, GLdouble x, GLdouble y, GLdouble z) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform3d, location, x, y, z)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, Uniform4d, GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform4d, location, x, y, z, w)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, Uniform1dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform1dv, location, count, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, Uniform2dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform2dv, location, count, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, Uniform3dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform3dv, location, count, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, Uniform4dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, Uniform4dv, location, count, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix2dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix2dv, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix3dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix3dv, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix4dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix4dv, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix2x3dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix2x3dv, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix2x4dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix2x4dv, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix3x2dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix3x2dv, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix3x4dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix3x4dv, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix4x2dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix4x2dv, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, UniformMatrix4x3dv, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformMatrix4x3dv, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetUniformdv, GLuint program, GLint location, GLdouble* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetUniformdv, program, location, params)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(GLint, GetSubroutineUniformLocation, GLuint program, GLenum shadertype, const GLchar* name) DECLARE_GL_FUNCTION_STUB_END(GLint, GetSubroutineUniformLocation, program, shadertype, name)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(GLuint, GetSubroutineIndex, GLuint program, GLenum shadertype, const GLchar* name) DECLARE_GL_FUNCTION_STUB_END(GLuint, GetSubroutineIndex, program, shadertype, name)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetActiveSubroutineUniformiv, GLuint program, GLenum shadertype, GLuint index, GLenum pname, GLint* values) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetActiveSubroutineUniformiv, program, shadertype, index, pname, values)
|
||||
@@ -942,28 +936,28 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, UniformSubroutinesuiv, GLenum shadertype, GL
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetUniformSubroutineuiv, GLenum shadertype, GLint location, GLuint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetUniformSubroutineuiv, shadertype, location, params)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetProgramStageiv, GLuint program, GLenum shadertype, GLenum pname, GLint* values) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetProgramStageiv, program, shadertype, pname, values)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PatchParameterfv, GLenum pname, const GLfloat* values) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PatchParameterfv, pname, values)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedback, GLenum mode, GLuint id) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedback, mode, id)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedbackStream, GLenum mode, GLuint id, GLuint stream) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedbackStream, mode, id, stream)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BeginQueryIndexed, GLenum target, GLuint index, GLuint id) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BeginQueryIndexed, target, index, id)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, EndQueryIndexed, GLenum target, GLuint index) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, EndQueryIndexed, target, index)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetQueryIndexediv, GLenum target, GLuint index, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetQueryIndexediv, target, index, pname, params)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform1d, GLuint program, GLint location, GLdouble v0) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform1d, program, location, v0)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform1dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform1dv, program, location, count, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform2d, GLuint program, GLint location, GLdouble v0, GLdouble v1) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform2d, program, location, v0, v1)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform2dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform2dv, program, location, count, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform3d, GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform3d, program, location, v0, v1, v2)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform3dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform3dv, program, location, count, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform4d, GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform4d, program, location, v0, v1, v2, v3)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniform4dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniform4dv, program, location, count, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix2dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix2dv, program, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix3dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix3dv, program, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix4dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix4dv, program, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix2x3dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix2x3dv, program, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix3x2dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix3x2dv, program, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix2x4dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix2x4dv, program, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix4x2dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix4x2dv, program, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix3x4dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix3x4dv, program, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ProgramUniformMatrix4x3dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ProgramUniformMatrix4x3dv, program, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, DrawTransformFeedback, GLenum mode, GLuint id) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawTransformFeedback, mode, id)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, DrawTransformFeedbackStream, GLenum mode, GLuint id, GLuint stream) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawTransformFeedbackStream, mode, id, stream)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, BeginQueryIndexed, GLenum target, GLuint index, GLuint id) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BeginQueryIndexed, target, index, id)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, EndQueryIndexed, GLenum target, GLuint index) DECLARE_GL_FUNCTION_END_NO_RETURN(void, EndQueryIndexed, target, index)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetQueryIndexediv, GLenum target, GLuint index, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryIndexediv, target, index, pname, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform1d, GLuint program, GLint location, GLdouble v0) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform1d, program, location, v0)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform1dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform1dv, program, location, count, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform2d, GLuint program, GLint location, GLdouble v0, GLdouble v1) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform2d, program, location, v0, v1)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform2dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform2dv, program, location, count, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform3d, GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform3d, program, location, v0, v1, v2)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform3dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform3dv, program, location, count, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform4d, GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform4d, program, location, v0, v1, v2, v3)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform4dv, GLuint program, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform4dv, program, location, count, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix2dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix2dv, program, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix3dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix3dv, program, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix4dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix4dv, program, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix2x3dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix2x3dv, program, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix3x2dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix3x2dv, program, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix2x4dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix2x4dv, program, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix4x2dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix4x2dv, program, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix3x4dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix3x4dv, program, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix4x3dv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix4x3dv, program, location, count, transpose, value)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribL1d, GLuint index, GLdouble x) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribL1d, index, x)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribL2d, GLuint index, GLdouble x, GLdouble y) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribL2d, index, x, y)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribL3d, GLuint index, GLdouble x, GLdouble y, GLdouble z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribL3d, index, x, y, z)
|
||||
@@ -988,8 +982,8 @@ DECLARE_GL_FUNCTION_HEAD(void, DrawArraysInstancedBaseInstance, GLenum mode, GLi
|
||||
DECLARE_GL_FUNCTION_HEAD(void, DrawElementsInstancedBaseInstance, GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLuint baseinstance) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawElementsInstancedBaseInstance, mode, count, type, indices, instancecount, baseinstance)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, DrawElementsInstancedBaseVertexBaseInstance, GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLint basevertex, GLuint baseinstance) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawElementsInstancedBaseVertexBaseInstance, mode, count, type, indices, instancecount, basevertex, baseinstance)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetActiveAtomicCounterBufferiv, GLuint program, GLuint bufferIndex, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetActiveAtomicCounterBufferiv, program, bufferIndex, pname, params)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedbackInstanced, GLenum mode, GLuint id, GLsizei instancecount) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedbackInstanced, mode, id, instancecount)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedbackStreamInstanced, GLenum mode, GLuint id, GLuint stream, GLsizei instancecount) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedbackStreamInstanced, mode, id, stream, instancecount)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, DrawTransformFeedbackInstanced, GLenum mode, GLuint id, GLsizei instancecount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawTransformFeedbackInstanced, mode, id, instancecount)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, DrawTransformFeedbackStreamInstanced, GLenum mode, GLuint id, GLuint stream, GLsizei instancecount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawTransformFeedbackStreamInstanced, mode, id, stream, instancecount)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearBufferData, GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearBufferData, target, internalformat, format, type, data)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearBufferSubData, GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearBufferSubData, target, internalformat, offset, size, format, type, data)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetInternalformati64v, GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint64* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetInternalformati64v, target, internalformat, pname, count, params)
|
||||
|
||||
@@ -1030,6 +1030,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*params = obj ? static_cast<GLint>(obj->GetExternalIndex()) : 0;
|
||||
return;
|
||||
}
|
||||
case GL_DRAW_INDIRECT_BUFFER_BINDING: {
|
||||
auto& obj = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
|
||||
*params = obj ? static_cast<GLint>(obj->GetExternalIndex()) : 0;
|
||||
return;
|
||||
}
|
||||
case GL_MAX_DEBUG_GROUP_STACK_DEPTH:
|
||||
*params = 0; // debug-group entrypoints are stubbed
|
||||
return;
|
||||
@@ -1912,6 +1917,21 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
case GL_MAX_SAMPLE_MASK_WORDS:
|
||||
*params = dynamicParameters.MaxSampleMaskWords;
|
||||
break;
|
||||
case GL_PATCH_VERTICES:
|
||||
*params = static_cast<GLint>(MG_State::pGLContext->GetPatchVertices());
|
||||
break;
|
||||
case GL_MAX_PATCH_VERTICES:
|
||||
*params = dynamicParameters.MaxPatchVertices;
|
||||
break;
|
||||
case GL_MAX_TESS_GEN_LEVEL:
|
||||
*params = dynamicParameters.MaxTessGenLevel;
|
||||
break;
|
||||
case GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET:
|
||||
*params = dynamicParameters.MinProgramTextureGatherOffset;
|
||||
break;
|
||||
case GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET:
|
||||
*params = dynamicParameters.MaxProgramTextureGatherOffset;
|
||||
break;
|
||||
case GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS:
|
||||
*params = static_cast<GLint>(GetIndexedBufferQueryPointCount(BufferTarget::ShaderStorage));
|
||||
break;
|
||||
@@ -1941,7 +1961,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*params = MG_State::pGLContext->IsTransformFeedbackActive() ? 1 : 0;
|
||||
break;
|
||||
case GL_TRANSFORM_FEEDBACK_PAUSED:
|
||||
*params = 0;
|
||||
*params = MG_State::pGLContext->IsTransformFeedbackPaused() ? 1 : 0;
|
||||
break;
|
||||
case GL_TRANSFORM_FEEDBACK_BINDING:
|
||||
*params = static_cast<GLint>(MG_State::pGLContext->GetBoundTransformFeedbackName());
|
||||
break;
|
||||
case GL_MAX_TEXTURE_IMAGE_UNITS:
|
||||
*params = dynamicParameters.MaxTextureImageUnits;
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#include "GL_Program.h"
|
||||
#include "Config.h"
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <MG_Impl/GLImpl/VertexArray/Validators.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
||||
@@ -325,11 +327,16 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
|
||||
void DeleteProgram_State(GLuint program) {
|
||||
// "If program is zero, it is silently ignored" (GL 4.6 core 7.3) - unlike every
|
||||
// other program entry point, where 0 is a name GL never handed out.
|
||||
if (program == 0) return;
|
||||
if (!CheckProgramNameValidity(program)) return;
|
||||
MG_State::pGLContext->MarkProgramForDeletion(program);
|
||||
}
|
||||
|
||||
void DeleteShader_State(GLuint shader) {
|
||||
// Same silent-zero rule as glDeleteProgram (GL 4.6 core 7.1).
|
||||
if (shader == 0) return;
|
||||
if (!CheckShaderNameValidity(shader)) return;
|
||||
MG_State::pGLContext->MarkShaderForDeletion(shader);
|
||||
}
|
||||
@@ -643,6 +650,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
|
||||
case GL_PROGRAM_BINARY_LENGTH:
|
||||
// No program binary format is exposed, so a program never has a retrievable
|
||||
// binary and its length is zero (ARB_get_program_binary).
|
||||
*params = 0;
|
||||
break;
|
||||
case GL_PROGRAM_BINARY_RETRIEVABLE_HINT:
|
||||
*params = programObject->GetBinaryRetrievableHint() ? GL_TRUE : GL_FALSE;
|
||||
break;
|
||||
|
||||
case GL_GEOMETRY_VERTICES_OUT:
|
||||
case GL_GEOMETRY_INPUT_TYPE:
|
||||
@@ -818,7 +832,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<T, GLfloat>) {
|
||||
if (ttype->isMatrix() && ttype->getMatrixCols() == 3) {
|
||||
if (ttype->getBasicType() != glslang::EbtDouble && ttype->isMatrix() &&
|
||||
ttype->getMatrixCols() == 3) {
|
||||
auto* pBase = pUBO + offset;
|
||||
for (int i = 0; i < ttype->getMatrixRows(); i++) {
|
||||
Memcpy(reinterpret_cast<char*>(params) + ttype->getMatrixCols() * sizeof(GLfloat) * i,
|
||||
@@ -828,9 +843,46 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
}
|
||||
|
||||
// A double-precision uniform is the one case where the stored component type can
|
||||
// differ from the queried one for a non-opaque uniform, and the difference is not
|
||||
// just a reinterpretation: it is twice as wide, so a raw copy would overrun the
|
||||
// caller's buffer as well as return nonsense. Read component by component and let
|
||||
// GL's conversion rules (7.6: round to nearest for the integer queries) apply.
|
||||
if (ttype->getBasicType() == glslang::EbtDouble) {
|
||||
const Int columns = ttype->isMatrix() ? ttype->getMatrixCols() : 1;
|
||||
const Int rows = ttype->isMatrix() ? ttype->getMatrixRows()
|
||||
: (ttype->isVector() ? ttype->getVectorSize() : 1);
|
||||
// The slot the linker handed out is exactly `columns` columns wide, so it also
|
||||
// states the column stride - which for a double matrix is not a float's 16 bytes.
|
||||
const SizeT columnStride = columns > 0 ? size / static_cast<SizeT>(columns) : size;
|
||||
for (Int column = 0; column < columns; ++column) {
|
||||
for (Int row = 0; row < rows; ++row) {
|
||||
GLdouble component = 0.0;
|
||||
Memcpy(&component, pUBO + offset + column * columnStride + row * sizeof(GLdouble),
|
||||
sizeof(component));
|
||||
if constexpr (std::is_integral_v<T>) {
|
||||
// Rounded to the nearest integer and clamped into the queried type's
|
||||
// range, so a negative double read through glGetUniformuiv is 0
|
||||
// rather than its two's complement.
|
||||
const GLdouble rounded = std::nearbyint(component);
|
||||
const GLdouble lowest = static_cast<GLdouble>(std::numeric_limits<T>::lowest());
|
||||
const GLdouble highest = static_cast<GLdouble>(std::numeric_limits<T>::max());
|
||||
params[column * rows + row] = static_cast<T>(std::clamp(rounded, lowest, highest));
|
||||
} else {
|
||||
params[column * rows + row] = static_cast<T>(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Memcpy(params, pUBO + offset, size);
|
||||
}
|
||||
|
||||
void GetUniformdv_State(GLuint program, GLint location, GLdouble* params) {
|
||||
GetUniformScalar_State(program, location, params);
|
||||
}
|
||||
|
||||
void GetUniformfv_State(GLuint program, GLint location, GLfloat* params) {
|
||||
GetUniformScalar_State(program, location, params);
|
||||
}
|
||||
@@ -915,9 +967,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
void UseProgram_State(GLuint program) {
|
||||
MGLOG_D("UseProgram_State: program=%u", program);
|
||||
|
||||
// GL 3.3 core 2.11.3: the program in use may not change while transform
|
||||
// feedback is active (there is no pause in 3.3).
|
||||
if (MG_State::pGLContext->IsTransformFeedbackActive()) {
|
||||
// The program in use may not change while transform feedback is active - unless
|
||||
// the capture is paused, which is exactly what ARB_transform_feedback2 added the
|
||||
// pause for (GL 4.6 core 7.3).
|
||||
if (MG_State::pGLContext->IsTransformFeedbackActive() &&
|
||||
!MG_State::pGLContext->IsTransformFeedbackPaused()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
@@ -1047,6 +1101,38 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
}
|
||||
|
||||
// glUniform*d / glUniformMatrix*dv. The vector forms need nothing beyond the shared
|
||||
// upload template - it is already typed on the component - but a matrix does: the
|
||||
// column stride the linker used for a double matrix is not the 16 bytes a float one
|
||||
// gets. It is not guessed here; the slot the uniform was given is exactly `columns`
|
||||
// columns wide, so dividing states the stride the rest of the pipeline agreed on.
|
||||
template <typename Program>
|
||||
void UniformMatrixdv_Object(Program& programObject, GLint location, GLsizei count, GLboolean transpose,
|
||||
const GLdouble* value, Int columns, Int rows) {
|
||||
const SizeT slotSize = programObject.GetUniformSizesInBytes(location);
|
||||
const SizeT columnStride = columns > 0 ? slotSize / static_cast<SizeT>(columns) : slotSize;
|
||||
const SizeT componentCount = static_cast<SizeT>(columns) * static_cast<SizeT>(rows);
|
||||
Vector<GLdouble> column(static_cast<SizeT>(rows));
|
||||
for (GLint matrix = 0; matrix < count; ++matrix) {
|
||||
if (matrix > 0 && !programObject.UniformLocationsAliasSameUniform(location, location + matrix)) break;
|
||||
if (!programObject.IsValidUniformLocation(location + matrix)) {
|
||||
RecordInvalidUniformLocationError(__func__, location + matrix, "the current program object");
|
||||
return;
|
||||
}
|
||||
const GLdouble* source = value + matrix * componentCount;
|
||||
for (Int c = 0; c < columns; ++c) {
|
||||
for (Int r = 0; r < rows; ++r) {
|
||||
column[r] = transpose == GL_TRUE ? source[r * columns + c] : source[c * rows + r];
|
||||
}
|
||||
Uniform_State<1>(programObject, location + matrix, column.data(), c * columnStride);
|
||||
for (Int r = 1; r < rows; ++r) {
|
||||
Uniform_State<1>(programObject, location + matrix, column.data() + r,
|
||||
c * columnStride + r * sizeof(GLdouble));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to transpose a 2x2 matrix
|
||||
void TransposeMatrix2x2(const GLfloat* input, GLfloat* output) {
|
||||
// Input matrix is in column-major order (OpenGL default)
|
||||
@@ -1855,6 +1941,312 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
GLuint v[] = {v0, v1, v2, v3};
|
||||
Uniform4uiv(location, 1, v);
|
||||
}
|
||||
void Uniform1d(GLint location, GLdouble v0) {
|
||||
const GLdouble v[] = {v0};
|
||||
Uniformv_State<1>(location, 1, v);
|
||||
}
|
||||
|
||||
void Uniform1dv(GLint location, GLsizei count, const GLdouble* value) {
|
||||
Uniformv_State<1>(location, count, value);
|
||||
}
|
||||
|
||||
void ProgramUniform1d(GLuint program, GLint location, GLdouble v0) {
|
||||
const GLdouble v[] = {v0};
|
||||
ProgramUniformv_State<1>(program, location, 1, v);
|
||||
}
|
||||
|
||||
void ProgramUniform1dv(GLuint program, GLint location, GLsizei count, const GLdouble* value) {
|
||||
ProgramUniformv_State<1>(program, location, count, value);
|
||||
}
|
||||
void Uniform2d(GLint location, GLdouble v0, GLdouble v1) {
|
||||
const GLdouble v[] = {v0, v1};
|
||||
Uniformv_State<2>(location, 1, v);
|
||||
}
|
||||
|
||||
void Uniform2dv(GLint location, GLsizei count, const GLdouble* value) {
|
||||
Uniformv_State<2>(location, count, value);
|
||||
}
|
||||
|
||||
void ProgramUniform2d(GLuint program, GLint location, GLdouble v0, GLdouble v1) {
|
||||
const GLdouble v[] = {v0, v1};
|
||||
ProgramUniformv_State<2>(program, location, 1, v);
|
||||
}
|
||||
|
||||
void ProgramUniform2dv(GLuint program, GLint location, GLsizei count, const GLdouble* value) {
|
||||
ProgramUniformv_State<2>(program, location, count, value);
|
||||
}
|
||||
void Uniform3d(GLint location, GLdouble v0, GLdouble v1, GLdouble v2) {
|
||||
const GLdouble v[] = {v0, v1, v2};
|
||||
Uniformv_State<3>(location, 1, v);
|
||||
}
|
||||
|
||||
void Uniform3dv(GLint location, GLsizei count, const GLdouble* value) {
|
||||
Uniformv_State<3>(location, count, value);
|
||||
}
|
||||
|
||||
void ProgramUniform3d(GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2) {
|
||||
const GLdouble v[] = {v0, v1, v2};
|
||||
ProgramUniformv_State<3>(program, location, 1, v);
|
||||
}
|
||||
|
||||
void ProgramUniform3dv(GLuint program, GLint location, GLsizei count, const GLdouble* value) {
|
||||
ProgramUniformv_State<3>(program, location, count, value);
|
||||
}
|
||||
void Uniform4d(GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3) {
|
||||
const GLdouble v[] = {v0, v1, v2, v3};
|
||||
Uniformv_State<4>(location, 1, v);
|
||||
}
|
||||
|
||||
void Uniform4dv(GLint location, GLsizei count, const GLdouble* value) {
|
||||
Uniformv_State<4>(location, count, value);
|
||||
}
|
||||
|
||||
void ProgramUniform4d(GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3) {
|
||||
const GLdouble v[] = {v0, v1, v2, v3};
|
||||
ProgramUniformv_State<4>(program, location, 1, v);
|
||||
}
|
||||
|
||||
void ProgramUniform4dv(GLuint program, GLint location, GLsizei count, const GLdouble* value) {
|
||||
ProgramUniformv_State<4>(program, location, count, value);
|
||||
}
|
||||
void UniformMatrix2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
|
||||
if (location == -1) return;
|
||||
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
|
||||
if (programObject == nullptr) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "There is no current program object."));
|
||||
return;
|
||||
}
|
||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 2, 2);
|
||||
}
|
||||
|
||||
void ProgramUniformMatrix2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||
const GLdouble* value) {
|
||||
if (location == -1) return;
|
||||
auto& programObject = TryToGetProgramObject(program);
|
||||
if (!programObject) return;
|
||||
if (!programObject->GetLinkStatus()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"program " + std::to_string(program) + " is not linked."));
|
||||
return;
|
||||
}
|
||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 2, 2);
|
||||
}
|
||||
void UniformMatrix3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
|
||||
if (location == -1) return;
|
||||
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
|
||||
if (programObject == nullptr) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "There is no current program object."));
|
||||
return;
|
||||
}
|
||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 3, 3);
|
||||
}
|
||||
|
||||
void ProgramUniformMatrix3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||
const GLdouble* value) {
|
||||
if (location == -1) return;
|
||||
auto& programObject = TryToGetProgramObject(program);
|
||||
if (!programObject) return;
|
||||
if (!programObject->GetLinkStatus()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"program " + std::to_string(program) + " is not linked."));
|
||||
return;
|
||||
}
|
||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 3, 3);
|
||||
}
|
||||
void UniformMatrix4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
|
||||
if (location == -1) return;
|
||||
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
|
||||
if (programObject == nullptr) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "There is no current program object."));
|
||||
return;
|
||||
}
|
||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 4, 4);
|
||||
}
|
||||
|
||||
void ProgramUniformMatrix4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||
const GLdouble* value) {
|
||||
if (location == -1) return;
|
||||
auto& programObject = TryToGetProgramObject(program);
|
||||
if (!programObject) return;
|
||||
if (!programObject->GetLinkStatus()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"program " + std::to_string(program) + " is not linked."));
|
||||
return;
|
||||
}
|
||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 4, 4);
|
||||
}
|
||||
void UniformMatrix2x3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
|
||||
if (location == -1) return;
|
||||
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
|
||||
if (programObject == nullptr) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "There is no current program object."));
|
||||
return;
|
||||
}
|
||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 2, 3);
|
||||
}
|
||||
|
||||
void ProgramUniformMatrix2x3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||
const GLdouble* value) {
|
||||
if (location == -1) return;
|
||||
auto& programObject = TryToGetProgramObject(program);
|
||||
if (!programObject) return;
|
||||
if (!programObject->GetLinkStatus()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"program " + std::to_string(program) + " is not linked."));
|
||||
return;
|
||||
}
|
||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 2, 3);
|
||||
}
|
||||
void UniformMatrix2x4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
|
||||
if (location == -1) return;
|
||||
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
|
||||
if (programObject == nullptr) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "There is no current program object."));
|
||||
return;
|
||||
}
|
||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 2, 4);
|
||||
}
|
||||
|
||||
void ProgramUniformMatrix2x4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||
const GLdouble* value) {
|
||||
if (location == -1) return;
|
||||
auto& programObject = TryToGetProgramObject(program);
|
||||
if (!programObject) return;
|
||||
if (!programObject->GetLinkStatus()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"program " + std::to_string(program) + " is not linked."));
|
||||
return;
|
||||
}
|
||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 2, 4);
|
||||
}
|
||||
void UniformMatrix3x2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
|
||||
if (location == -1) return;
|
||||
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
|
||||
if (programObject == nullptr) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "There is no current program object."));
|
||||
return;
|
||||
}
|
||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 3, 2);
|
||||
}
|
||||
|
||||
void ProgramUniformMatrix3x2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||
const GLdouble* value) {
|
||||
if (location == -1) return;
|
||||
auto& programObject = TryToGetProgramObject(program);
|
||||
if (!programObject) return;
|
||||
if (!programObject->GetLinkStatus()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"program " + std::to_string(program) + " is not linked."));
|
||||
return;
|
||||
}
|
||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 3, 2);
|
||||
}
|
||||
void UniformMatrix3x4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
|
||||
if (location == -1) return;
|
||||
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
|
||||
if (programObject == nullptr) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "There is no current program object."));
|
||||
return;
|
||||
}
|
||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 3, 4);
|
||||
}
|
||||
|
||||
void ProgramUniformMatrix3x4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||
const GLdouble* value) {
|
||||
if (location == -1) return;
|
||||
auto& programObject = TryToGetProgramObject(program);
|
||||
if (!programObject) return;
|
||||
if (!programObject->GetLinkStatus()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"program " + std::to_string(program) + " is not linked."));
|
||||
return;
|
||||
}
|
||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 3, 4);
|
||||
}
|
||||
void UniformMatrix4x2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
|
||||
if (location == -1) return;
|
||||
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
|
||||
if (programObject == nullptr) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "There is no current program object."));
|
||||
return;
|
||||
}
|
||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 4, 2);
|
||||
}
|
||||
|
||||
void ProgramUniformMatrix4x2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||
const GLdouble* value) {
|
||||
if (location == -1) return;
|
||||
auto& programObject = TryToGetProgramObject(program);
|
||||
if (!programObject) return;
|
||||
if (!programObject->GetLinkStatus()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"program " + std::to_string(program) + " is not linked."));
|
||||
return;
|
||||
}
|
||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 4, 2);
|
||||
}
|
||||
void UniformMatrix4x3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
|
||||
if (location == -1) return;
|
||||
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
|
||||
if (programObject == nullptr) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "There is no current program object."));
|
||||
return;
|
||||
}
|
||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 4, 3);
|
||||
}
|
||||
|
||||
void ProgramUniformMatrix4x3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose,
|
||||
const GLdouble* value) {
|
||||
if (location == -1) return;
|
||||
auto& programObject = TryToGetProgramObject(program);
|
||||
if (!programObject) return;
|
||||
if (!programObject->GetLinkStatus()) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"program " + std::to_string(program) + " is not linked."));
|
||||
return;
|
||||
}
|
||||
UniformMatrixdv_Object(*programObject, location, count, transpose, value, 4, 3);
|
||||
}
|
||||
void GetUniformdv(GLuint program, GLint location, GLdouble* params) {
|
||||
GetUniformdv_State(program, location, params);
|
||||
}
|
||||
|
||||
void Uniform1fv(GLint location, GLsizei count, const GLfloat* value) {
|
||||
Uniform1fv_State(location, count, value);
|
||||
}
|
||||
@@ -2251,6 +2643,65 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
ValidateProgram_State(program);
|
||||
}
|
||||
|
||||
// ARB_get_program_binary with no supported binary format (GL_NUM_PROGRAM_BINARY_FORMATS
|
||||
// is 0, which the extension explicitly allows). The three entry points below are what an
|
||||
// application - and dEQP's function loader - reach through the extension; without it
|
||||
// glProgramParameteri is not exposed in a 4.0 context at all.
|
||||
void ProgramParameteri(GLuint program, GLenum pname, GLint value) {
|
||||
auto& programObject = TryToGetProgramObject(program);
|
||||
if (!programObject) return;
|
||||
if (pname != GL_PROGRAM_BINARY_RETRIEVABLE_HINT) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "pname is not an accepted value."));
|
||||
return;
|
||||
}
|
||||
if (value != GL_TRUE && value != GL_FALSE) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "value must be GL_TRUE or GL_FALSE."));
|
||||
return;
|
||||
}
|
||||
programObject->SetBinaryRetrievableHint(value == GL_TRUE);
|
||||
}
|
||||
|
||||
void GetProgramBinary(GLuint program, GLsizei bufSize, GLsizei* length, GLenum* binaryFormat, void* binary) {
|
||||
(void)binaryFormat;
|
||||
(void)binary;
|
||||
auto& programObject = TryToGetProgramObject(program);
|
||||
if (!programObject) return;
|
||||
if (bufSize < 0) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "bufSize must be non-negative."));
|
||||
return;
|
||||
}
|
||||
if (length) *length = 0;
|
||||
// GL_PROGRAM_BINARY_LENGTH is always zero here, which the spec makes an error to ask for.
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "The program has no retrievable binary."));
|
||||
}
|
||||
|
||||
void ProgramBinary(GLuint program, GLenum binaryFormat, const void* binary, GLsizei length) {
|
||||
(void)binaryFormat;
|
||||
(void)binary;
|
||||
auto& programObject = TryToGetProgramObject(program);
|
||||
if (!programObject) return;
|
||||
if (length < 0) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "length must be non-negative."));
|
||||
return;
|
||||
}
|
||||
// No format is supported, so every binary is rejected - and the program's link status
|
||||
// has to read FALSE afterwards.
|
||||
programObject->MarkLinkFailedByProgramBinary();
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "binaryFormat is not a supported format."));
|
||||
}
|
||||
|
||||
void TransformFeedbackVaryings(GLuint program, GLsizei count, const GLchar* const* varyings, GLenum bufferMode) {
|
||||
auto& programObject = TryToGetProgramObject(program);
|
||||
if (!programObject) return;
|
||||
@@ -2279,6 +2730,31 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
for (GLsizei i = 0; i < count; ++i) {
|
||||
names.emplace_back(varyings != nullptr && varyings[i] != nullptr ? varyings[i] : "");
|
||||
}
|
||||
// ARB_transform_feedback3's special names only mean anything in an interleaved
|
||||
// capture, and gl_NextBuffer cannot advance past the last capture buffer.
|
||||
constexpr Uint maxTransformFeedbackBuffers = 4;
|
||||
Uint nextBufferCount = 0;
|
||||
for (const String& name : names) {
|
||||
const Bool isNextBuffer = name == "gl_NextBuffer";
|
||||
const Bool isSkipComponents = name.size() == 18 && name.compare(0, 17, "gl_SkipComponents") == 0 &&
|
||||
name[17] >= '1' && name[17] <= '4';
|
||||
if (!isNextBuffer && !isSkipComponents) continue;
|
||||
if (bufferMode != GL_INTERLEAVED_ATTRIBS) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"'" + name + "' requires GL_INTERLEAVED_ATTRIBS."));
|
||||
return;
|
||||
}
|
||||
if (isNextBuffer && ++nextBufferCount >= maxTransformFeedbackBuffers) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"More gl_NextBuffer entries than "
|
||||
"GL_MAX_TRANSFORM_FEEDBACK_BUFFERS allows."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
programObject->SetTransformFeedbackVaryings(Move(names), bufferMode);
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,45 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
GLint GetProgramResourceLocation(GLuint program, GLenum programInterface, const GLchar* name);
|
||||
GLint GetProgramResourceLocationIndex(GLuint program, GLenum programInterface, const GLchar* name);
|
||||
void ShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding);
|
||||
void Uniform1d(GLint location, GLdouble v0);
|
||||
void Uniform1dv(GLint location, GLsizei count, const GLdouble* value);
|
||||
void ProgramUniform1d(GLuint program, GLint location, GLdouble v0);
|
||||
void ProgramUniform1dv(GLuint program, GLint location, GLsizei count, const GLdouble* value);
|
||||
void Uniform2d(GLint location, GLdouble v0, GLdouble v1);
|
||||
void Uniform2dv(GLint location, GLsizei count, const GLdouble* value);
|
||||
void ProgramUniform2d(GLuint program, GLint location, GLdouble v0, GLdouble v1);
|
||||
void ProgramUniform2dv(GLuint program, GLint location, GLsizei count, const GLdouble* value);
|
||||
void Uniform3d(GLint location, GLdouble v0, GLdouble v1, GLdouble v2);
|
||||
void Uniform3dv(GLint location, GLsizei count, const GLdouble* value);
|
||||
void ProgramUniform3d(GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2);
|
||||
void ProgramUniform3dv(GLuint program, GLint location, GLsizei count, const GLdouble* value);
|
||||
void Uniform4d(GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3);
|
||||
void Uniform4dv(GLint location, GLsizei count, const GLdouble* value);
|
||||
void ProgramUniform4d(GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3);
|
||||
void ProgramUniform4dv(GLuint program, GLint location, GLsizei count, const GLdouble* value);
|
||||
void UniformMatrix2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||
void ProgramUniformMatrix2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||
void UniformMatrix3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||
void ProgramUniformMatrix3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||
void UniformMatrix4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||
void ProgramUniformMatrix4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||
void UniformMatrix2x3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||
void ProgramUniformMatrix2x3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||
void UniformMatrix2x4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||
void ProgramUniformMatrix2x4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||
void UniformMatrix3x2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||
void ProgramUniformMatrix3x2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||
void UniformMatrix3x4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||
void ProgramUniformMatrix3x4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||
void UniformMatrix4x2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||
void ProgramUniformMatrix4x2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||
void UniformMatrix4x3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||
void ProgramUniformMatrix4x3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value);
|
||||
void GetUniformdv(GLuint program, GLint location, GLdouble* params);
|
||||
void ValidateProgram(GLuint program);
|
||||
void ProgramParameteri(GLuint program, GLenum pname, GLint value);
|
||||
void GetProgramBinary(GLuint program, GLsizei bufSize, GLsizei* length, GLenum* binaryFormat, void* binary);
|
||||
void ProgramBinary(GLuint program, GLenum binaryFormat, const void* binary, GLsizei length);
|
||||
void TransformFeedbackVaryings(GLuint program, GLsizei count, const GLchar* const* varyings, GLenum bufferMode);
|
||||
void GetTransformFeedbackVarying(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLsizei* size,
|
||||
GLenum* type, GLchar* name);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
// End of Source File Header
|
||||
|
||||
#include "GL_Query.h"
|
||||
#include "../Getter/GL_Getter.h"
|
||||
#include <Config.h>
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
@@ -466,4 +467,42 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
*params = static_cast<GLuint64>(value);
|
||||
}
|
||||
|
||||
namespace {
|
||||
// The indexed query entry points differ from the plain ones only in the vertex
|
||||
// stream they address (GL 4.6 core 4.2.1): index must be below GL_MAX_VERTEX_STREAMS
|
||||
// for the two transform feedback targets and zero for every other target. With a
|
||||
// single vertex stream both bounds are 1, so a valid call is always index 0 and
|
||||
// forwards to the unindexed implementation.
|
||||
Bool ValidateQueryStreamIndex(const char* function, GLenum target, GLuint index) {
|
||||
const Bool perStreamTarget =
|
||||
target == GL_PRIMITIVES_GENERATED || target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN;
|
||||
GLint maxVertexStreams = 1;
|
||||
if (perStreamTarget) {
|
||||
GetIntegerv(GL_MAX_VERTEX_STREAMS, &maxVertexStreams);
|
||||
}
|
||||
if (index < static_cast<GLuint>(std::max(maxVertexStreams, 1))) {
|
||||
return true;
|
||||
}
|
||||
RecordQueryError(ErrorCode::InvalidValue, function,
|
||||
perStreamTarget ? "index is not less than GL_MAX_VERTEX_STREAMS."
|
||||
: "index must be zero for this query target.");
|
||||
return false;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void BeginQueryIndexed(GLenum target, GLuint index, GLuint id) {
|
||||
if (!ValidateQueryStreamIndex(__FUNCTION__, target, index)) return;
|
||||
BeginQuery(target, id);
|
||||
}
|
||||
|
||||
void EndQueryIndexed(GLenum target, GLuint index) {
|
||||
if (!ValidateQueryStreamIndex(__FUNCTION__, target, index)) return;
|
||||
EndQuery(target);
|
||||
}
|
||||
|
||||
void GetQueryIndexediv(GLenum target, GLuint index, GLenum pname, GLint* params) {
|
||||
if (!ValidateQueryStreamIndex(__FUNCTION__, target, index)) return;
|
||||
GetQueryiv(target, pname, params);
|
||||
}
|
||||
} // namespace MobileGL::MG_Impl::GLImpl
|
||||
|
||||
@@ -16,6 +16,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
void BeginQuery(GLenum target, GLuint id);
|
||||
void EndQuery(GLenum target);
|
||||
void GetQueryiv(GLenum target, GLenum pname, GLint* params);
|
||||
void BeginQueryIndexed(GLenum target, GLuint index, GLuint id);
|
||||
void EndQueryIndexed(GLenum target, GLuint index);
|
||||
void GetQueryIndexediv(GLenum target, GLuint index, GLenum pname, GLint* params);
|
||||
void GetQueryObjectiv(GLuint id, GLenum pname, GLint* params);
|
||||
void GetQueryObjectuiv(GLuint id, GLenum pname, GLuint* params);
|
||||
void GetQueryObjecti64v(GLuint id, GLenum pname, GLint64* params);
|
||||
|
||||
@@ -174,6 +174,21 @@ namespace MobileGL::MG_State::GLState {
|
||||
++m_changeSerial;
|
||||
}
|
||||
|
||||
void BufferObject::MarkGpuWritten() {
|
||||
m_gpuWritePending = true;
|
||||
}
|
||||
|
||||
void BufferObject::SyncGpuWrites() {
|
||||
if (!m_gpuWritePending) return;
|
||||
// Cleared unconditionally: without a readback op the shadow can never catch up,
|
||||
// and retrying on every subsequent read would only repeat the same no-op.
|
||||
m_gpuWritePending = false;
|
||||
if (m_size == 0 || g_bufferBackendOps == nullptr || g_bufferBackendOps->ReadbackFromGpu == nullptr) {
|
||||
return;
|
||||
}
|
||||
g_bufferBackendOps->ReadbackFromGpu(*this);
|
||||
}
|
||||
|
||||
void BufferObject::UploadSubData(DataPtr data, SizeT atOffset) {
|
||||
MOBILEGL_ASSERT(!m_isMapped || (m_mappingAccess & BufferMappingAccessBit::Persistent),
|
||||
"Cannot upload sub data while buffer is non-persistently mapped.");
|
||||
@@ -204,11 +219,13 @@ namespace MobileGL::MG_State::GLState {
|
||||
"Destination buffer copy out of bounds: dstOffset (%zu) + size (%zu) > m_size (%zu)", dstOffset,
|
||||
size, m_size);
|
||||
|
||||
src->SyncGpuWrites();
|
||||
Memcpy(m_resource.Bytes() + dstOffset, src->m_resource.Bytes() + srcOffset, size);
|
||||
NotifyContentWrite(dstOffset, size);
|
||||
}
|
||||
|
||||
void* BufferObject::AcquireMemory(Bool markMapped, Bool read, Bool write) {
|
||||
SyncGpuWrites();
|
||||
if (markMapped) {
|
||||
m_isMapped = true;
|
||||
m_mappingAccess = (read ? BufferMappingAccessBit::Read : BufferMappingAccessBit::Null) |
|
||||
@@ -250,6 +267,10 @@ namespace MobileGL::MG_State::GLState {
|
||||
MOBILEGL_ASSERT(range.end <= m_size && range.start <= range.end,
|
||||
"AcquireMemoryRange out of bounds: range (%zu, %zu) exceeds m_size (%zu)", range.start,
|
||||
range.end, m_size);
|
||||
// The app is about to look at the bytes; a shader may have rewritten them since
|
||||
// the shadow was last authoritative. Also needed for a write map without an
|
||||
// invalidate bit, whose staging copy is seeded from the shadow.
|
||||
SyncGpuWrites();
|
||||
m_isMapped = true;
|
||||
m_mappingAccess = access;
|
||||
m_mappedRange = range;
|
||||
|
||||
@@ -99,6 +99,13 @@ namespace MobileGL {
|
||||
// Must be idempotent: a second call for an already-backed buffer returns the
|
||||
// same base pointer.
|
||||
void* (*AcquirePersistentMap)(BufferObject& bufferObject) = nullptr;
|
||||
// Pulls the backend's current contents for the whole buffer into the shadow
|
||||
// (through WritebackFromBackend). Only ever called for a buffer the GPU may
|
||||
// have written behind the frontend's back - a shader storage or atomic counter
|
||||
// binding of a draw or dispatch - because nothing else can desynchronise the
|
||||
// shadow. Backends that cannot read their storage back leave this null; the
|
||||
// shadow then keeps its pre-dispatch bytes, which is the old behaviour.
|
||||
void (*ReadbackFromGpu)(BufferObject& bufferObject) = nullptr;
|
||||
};
|
||||
|
||||
// Registered by the active backend at init, cleared at shutdown.
|
||||
@@ -149,6 +156,16 @@ namespace MobileGL {
|
||||
// backend op: the backend storage already holds these bytes.
|
||||
void WritebackFromBackend(DataPtr data, SizeT atOffset);
|
||||
|
||||
// A draw or dispatch just ran with this buffer bound where a shader can write
|
||||
// it (shader storage / atomic counter). The next read has to reconcile with
|
||||
// that: pull the bytes back, or - when the shadow already IS coherent GPU
|
||||
// memory - wait for the work that wrote them to retire. Which of the two is
|
||||
// the backend's business; the flag only says a GPU write is outstanding.
|
||||
void MarkGpuWritten();
|
||||
// Refreshes the shadow from the backend when a GPU write is outstanding. Called
|
||||
// from every path that reads the shadow on the app's behalf.
|
||||
void SyncGpuWrites();
|
||||
|
||||
Bool IsMapped() const;
|
||||
Bool IsImmutableStorage() const;
|
||||
SizeT GetSize() const;
|
||||
@@ -196,6 +213,8 @@ namespace MobileGL {
|
||||
Bool m_isImmutableStorage = false;
|
||||
GLbitfield m_storageFlags = 0;
|
||||
Uint64 m_changeSerial = 0;
|
||||
// Set by MarkGpuWritten, cleared by SyncGpuWrites once the shadow is refreshed.
|
||||
Bool m_gpuWritePending = false;
|
||||
Range1D m_mappedRange;
|
||||
Vector<Uint8> m_stagingData;
|
||||
Bool m_ownsStagingData;
|
||||
|
||||
@@ -424,6 +424,14 @@ namespace MobileGL::MG_State {
|
||||
m_renderState.SetPointSize(size);
|
||||
}
|
||||
|
||||
void GLContext::SetPatchVertices(Uint vertices) {
|
||||
m_renderState.SetPatchVertices(vertices);
|
||||
}
|
||||
|
||||
Uint GLContext::GetPatchVertices() const {
|
||||
return m_renderState.GetPatchVertices();
|
||||
}
|
||||
|
||||
Float GLContext::GetPointSize() const {
|
||||
return m_renderState.GetPointSize();
|
||||
}
|
||||
@@ -745,6 +753,95 @@ namespace MobileGL::MG_State {
|
||||
Bool GLContext::ValidateRenderbufferObject(Uint index) const {
|
||||
return m_renderbufferState.ValidateRenderbufferObject(index);
|
||||
}
|
||||
|
||||
void GLContext::SaveBoundTransformFeedbackState() {
|
||||
auto& object = m_transformFeedbackObjects[m_boundTransformFeedback];
|
||||
for (Uint i = 0; i < MAX_TRANSFORM_FEEDBACK_BUFFERS; ++i) {
|
||||
const auto& point = m_bufferState.GetBindingPoint(BufferTarget::TransformFeedback, i);
|
||||
object.bindings[i] = {point.GetBoundObject(), point.GetRange(), point.HasExplicitRange()};
|
||||
}
|
||||
object.active = m_transformFeedbackActive;
|
||||
object.paused = m_transformFeedbackPaused;
|
||||
object.primitiveMode = m_transformFeedbackPrimitiveMode;
|
||||
object.program = m_transformFeedbackProgram;
|
||||
object.generation = m_transformFeedbackGeneration;
|
||||
object.capturedVertices = m_transformFeedbackCapturedVertices;
|
||||
object.inputPrimitives = m_transformFeedbackInputPrimitives;
|
||||
}
|
||||
|
||||
void GLContext::RestoreBoundTransformFeedbackState() {
|
||||
const auto& object = m_transformFeedbackObjects[m_boundTransformFeedback];
|
||||
for (Uint i = 0; i < MAX_TRANSFORM_FEEDBACK_BUFFERS; ++i) {
|
||||
auto& point = m_bufferState.GetBindingPoint(BufferTarget::TransformFeedback, i);
|
||||
point.Bind(object.bindings[i].buffer);
|
||||
if (object.bindings[i].buffer) {
|
||||
point.SetRange(object.bindings[i].range, object.bindings[i].hasExplicitRange);
|
||||
} else {
|
||||
point.ClearRange();
|
||||
}
|
||||
}
|
||||
m_transformFeedbackActive = object.active;
|
||||
m_transformFeedbackPaused = object.paused;
|
||||
m_transformFeedbackPrimitiveMode = object.primitiveMode;
|
||||
m_transformFeedbackProgram = object.program;
|
||||
// The generation identifies one capture span, and a span belongs to the object
|
||||
// that opened it - a backend keys its append state on it, so switching objects
|
||||
// has to bring the right one back.
|
||||
m_transformFeedbackGeneration = object.generation;
|
||||
m_transformFeedbackCapturedVertices = object.capturedVertices;
|
||||
m_transformFeedbackInputPrimitives = object.inputPrimitives;
|
||||
}
|
||||
|
||||
void GLContext::GenTransformFeedbackNames(Uint number, Vector<Uint>& ids) {
|
||||
ids.resize(number);
|
||||
if (number == 0) return;
|
||||
m_transformFeedbackNames.Generate(number, ids.data());
|
||||
// A generated name already denotes an object with the default state, so that a
|
||||
// bind never has to distinguish "first use" from any later one.
|
||||
for (const Uint id : ids) {
|
||||
m_transformFeedbackObjects[id] = {};
|
||||
}
|
||||
}
|
||||
|
||||
Bool GLContext::ValidateTransformFeedbackName(Uint index) const {
|
||||
return index == 0 || m_transformFeedbackNames.IsValid(index);
|
||||
}
|
||||
|
||||
void GLContext::BindTransformFeedbackObject(Uint index) {
|
||||
if (index == m_boundTransformFeedback) return;
|
||||
SaveBoundTransformFeedbackState();
|
||||
m_boundTransformFeedback = index;
|
||||
m_transformFeedbackObjects[index].everBound = true;
|
||||
RestoreBoundTransformFeedbackState();
|
||||
}
|
||||
|
||||
Bool GLContext::IsTransformFeedbackObject(Uint index) const {
|
||||
if (index == 0 || !m_transformFeedbackNames.IsValid(index)) return false;
|
||||
const auto it = m_transformFeedbackObjects.find(index);
|
||||
return it != m_transformFeedbackObjects.end() && it->second.everBound;
|
||||
}
|
||||
|
||||
void GLContext::MarkTransformFeedbackObjectForDeletion(Uint index) {
|
||||
if (index == 0 || !m_transformFeedbackNames.IsValid(index)) return;
|
||||
// Deleting the bound object reverts to the default one (GL 4.6 core 13.2.1);
|
||||
// its state is dropped rather than saved back into the dying object.
|
||||
if (index == m_boundTransformFeedback) {
|
||||
m_boundTransformFeedback = 0;
|
||||
RestoreBoundTransformFeedbackState();
|
||||
}
|
||||
m_transformFeedbackObjects.erase(index);
|
||||
m_transformFeedbackNames.Delete(index);
|
||||
}
|
||||
|
||||
Uint64 GLContext::GetTransformFeedbackRecordedVertices(Uint index) const {
|
||||
const auto it = m_transformFeedbackObjects.find(index);
|
||||
return it == m_transformFeedbackObjects.end() ? 0 : it->second.recordedVertices;
|
||||
}
|
||||
|
||||
Bool GLContext::HasTransformFeedbackCompletedSpan(Uint index) const {
|
||||
const auto it = m_transformFeedbackObjects.find(index);
|
||||
return it != m_transformFeedbackObjects.end() && it->second.hasCompletedSpan;
|
||||
}
|
||||
} // namespace GLState
|
||||
|
||||
// Leak-at-exit storage; see GlobalObjects.cpp.
|
||||
|
||||
@@ -138,6 +138,8 @@ namespace MobileGL {
|
||||
Float GetLineWidth() const;
|
||||
void SetPointSize(Float size);
|
||||
Float GetPointSize() const;
|
||||
void SetPatchVertices(Uint vertices);
|
||||
Uint GetPatchVertices() const;
|
||||
void SetPolygonOffset(Float factor, Float units);
|
||||
Float GetPolygonOffsetFactor() const;
|
||||
Float GetPolygonOffsetUnits() const;
|
||||
@@ -211,20 +213,30 @@ namespace MobileGL {
|
||||
void SetScissorBox(IntVec4 box); // x, y, width, height
|
||||
const IntVec4& GetScissorBox() const; // x, y, width, height
|
||||
|
||||
// Transform feedback (GL 3.0 core Begin/End; no feedback objects yet)
|
||||
// Transform feedback. The fields below are the state of the transform
|
||||
// feedback object currently bound to GL_TRANSFORM_FEEDBACK; see the object
|
||||
// block further down for how a bind swaps them.
|
||||
void BeginTransformFeedback(GLenum primitiveMode, const SharedPtr<ProgramObject>& program) {
|
||||
m_transformFeedbackActive = true;
|
||||
m_transformFeedbackPaused = false;
|
||||
m_transformFeedbackPrimitiveMode = primitiveMode;
|
||||
m_transformFeedbackProgram = program;
|
||||
++m_transformFeedbackGeneration;
|
||||
m_transformFeedbackGeneration = ++m_transformFeedbackNextGeneration;
|
||||
m_transformFeedbackCapturedVertices = 0;
|
||||
m_transformFeedbackInputPrimitives = 0;
|
||||
}
|
||||
void EndTransformFeedback() {
|
||||
m_transformFeedbackActive = false;
|
||||
m_transformFeedbackPaused = false;
|
||||
m_transformFeedbackProgram.reset();
|
||||
// What glDrawTransformFeedback on this object replays from now on.
|
||||
auto& object = m_transformFeedbackObjects[m_boundTransformFeedback];
|
||||
object.recordedVertices = m_transformFeedbackCapturedVertices;
|
||||
object.hasCompletedSpan = true;
|
||||
}
|
||||
Bool IsTransformFeedbackActive() const { return m_transformFeedbackActive; }
|
||||
Bool IsTransformFeedbackPaused() const { return m_transformFeedbackPaused; }
|
||||
void SetTransformFeedbackPaused(Bool paused) { m_transformFeedbackPaused = paused; }
|
||||
GLenum GetTransformFeedbackPrimitiveMode() const { return m_transformFeedbackPrimitiveMode; }
|
||||
const SharedPtr<ProgramObject>& GetTransformFeedbackProgram() const {
|
||||
return m_transformFeedbackProgram;
|
||||
@@ -239,6 +251,15 @@ namespace MobileGL {
|
||||
m_transformFeedbackPrimitiveCounter += primitives;
|
||||
}
|
||||
Uint64 GetTransformFeedbackPrimitiveCounter() const { return m_transformFeedbackPrimitiveCounter; }
|
||||
// Primitives a draw assembled while the capture was paused. GL counts those in
|
||||
// PRIMITIVES_GENERATED, but a backend that answers the query with its own
|
||||
// transform feedback counter cannot see them - nothing was being captured.
|
||||
void AddTransformFeedbackPausedPrimitives(Uint64 primitives) {
|
||||
m_transformFeedbackPausedPrimitiveCounter += primitives;
|
||||
}
|
||||
Uint64 GetTransformFeedbackPausedPrimitiveCounter() const {
|
||||
return m_transformFeedbackPausedPrimitiveCounter;
|
||||
}
|
||||
// Vertices already captured since BeginTransformFeedback (drives the
|
||||
// buffer-capacity clamp on the primitives-written accounting).
|
||||
void AddTransformFeedbackCapturedVertices(Uint64 vertices) {
|
||||
@@ -252,6 +273,32 @@ namespace MobileGL {
|
||||
}
|
||||
Uint64 GetTransformFeedbackInputPrimitives() const { return m_transformFeedbackInputPrimitives; }
|
||||
|
||||
// Transform feedback objects (ARB_transform_feedback2 / GL 4.0 core).
|
||||
// The capture state above and the indexed GL_TRANSFORM_FEEDBACK_BUFFER
|
||||
// binding points are object state, but the context keeps exactly one live
|
||||
// copy of both so that every existing reader - the backends' per-draw sync,
|
||||
// the drawing and getter paths - needs no notion of which object owns them.
|
||||
// A bind therefore saves the live copy into the outgoing object and restores
|
||||
// the incoming one's. Object 0 is the default object and always exists.
|
||||
static constexpr Uint MAX_TRANSFORM_FEEDBACK_BUFFERS = 4;
|
||||
void GenTransformFeedbackNames(Uint number, Vector<Uint>& ids);
|
||||
// A name glGenTransformFeedbacks handed out and glDeleteTransformFeedbacks
|
||||
// has not taken back. Name 0 is always valid.
|
||||
Bool ValidateTransformFeedbackName(Uint index) const;
|
||||
// What glIsTransformFeedback reports: a generated name only becomes the name
|
||||
// of an object once it has been bound at least once (GL 4.6 core 13.2.1).
|
||||
Bool IsTransformFeedbackObject(Uint index) const;
|
||||
void BindTransformFeedbackObject(Uint index);
|
||||
void MarkTransformFeedbackObjectForDeletion(Uint index);
|
||||
Uint GetBoundTransformFeedbackName() const { return m_boundTransformFeedback; }
|
||||
// Vertices the object captured in its last completed span; the vertex count
|
||||
// glDrawTransformFeedback replays.
|
||||
Uint64 GetTransformFeedbackRecordedVertices(Uint index) const;
|
||||
// Whether the object has ever completed a capture span. glDrawTransformFeedback
|
||||
// on an object that has not is INVALID_OPERATION, which a zero vertex count
|
||||
// cannot express: an empty completed span is legal and draws nothing.
|
||||
Bool HasTransformFeedbackCompletedSpan(Uint index) const;
|
||||
|
||||
// Framebuffer
|
||||
void GenFramebufferNames(Uint number, Vector<Uint>& framebuffers);
|
||||
const SharedPtr<FramebufferObject>& GetFramebufferObject(Uint index);
|
||||
@@ -285,12 +332,45 @@ namespace MobileGL {
|
||||
VertexArrayState m_vertexArrayState;
|
||||
Array<CurrentVertexAttributeValue, VertexArrayObject::MAX_VERTEX_ATTRIBS> m_currentVertexAttributes{};
|
||||
Bool m_transformFeedbackActive = false;
|
||||
Bool m_transformFeedbackPaused = false;
|
||||
GLenum m_transformFeedbackPrimitiveMode = GL_POINTS;
|
||||
SharedPtr<ProgramObject> m_transformFeedbackProgram;
|
||||
Uint64 m_transformFeedbackGeneration = 0;
|
||||
// Source of the per-span ids above; never rolls back with an object switch.
|
||||
Uint64 m_transformFeedbackNextGeneration = 0;
|
||||
// Not object state: the transform feedback queries snapshot it at BeginQuery
|
||||
// and take the delta at EndQuery, which spans whatever objects were used.
|
||||
Uint64 m_transformFeedbackPrimitiveCounter = 0;
|
||||
Uint64 m_transformFeedbackPausedPrimitiveCounter = 0;
|
||||
Uint64 m_transformFeedbackCapturedVertices = 0;
|
||||
Uint64 m_transformFeedbackInputPrimitives = 0;
|
||||
|
||||
// Everything a transform feedback object owns while it is NOT the bound one.
|
||||
struct TransformFeedbackObjectState {
|
||||
struct SavedBufferBinding {
|
||||
SharedPtr<BufferObject> buffer;
|
||||
Range1D range;
|
||||
Bool hasExplicitRange = false;
|
||||
};
|
||||
Array<SavedBufferBinding, MAX_TRANSFORM_FEEDBACK_BUFFERS> bindings;
|
||||
Bool active = false;
|
||||
Bool paused = false;
|
||||
GLenum primitiveMode = GL_POINTS;
|
||||
SharedPtr<ProgramObject> program;
|
||||
Uint64 generation = 0;
|
||||
Uint64 capturedVertices = 0;
|
||||
Uint64 inputPrimitives = 0;
|
||||
Uint64 recordedVertices = 0;
|
||||
Bool hasCompletedSpan = false;
|
||||
Bool everBound = false;
|
||||
};
|
||||
void SaveBoundTransformFeedbackState();
|
||||
void RestoreBoundTransformFeedbackState();
|
||||
// operator[] materialises an entry with the default state on first touch, so
|
||||
// the default object (name 0) needs no seeding here.
|
||||
UnorderedMap<Uint, TransformFeedbackObjectState> m_transformFeedbackObjects;
|
||||
IndexGenerator<Uint> m_transformFeedbackNames;
|
||||
Uint m_boundTransformFeedback = 0;
|
||||
TextureState m_textureState;
|
||||
ProgramState m_programState;
|
||||
RenderState m_renderState;
|
||||
|
||||
@@ -174,6 +174,9 @@ namespace MobileGL::MG_State::GLState {
|
||||
m_xfbStrides.clear();
|
||||
m_xfbBufferMode = GL_INTERLEAVED_ATTRIBS;
|
||||
m_xfbVaryingNameMaxLength = 0;
|
||||
m_xfbNeedsScatteredCapture = false;
|
||||
m_xfbPackedStride = 0;
|
||||
m_gsInputPrimitive = GL_NONE;
|
||||
m_linkStatus = false;
|
||||
}
|
||||
|
||||
@@ -222,6 +225,8 @@ namespace MobileGL::MG_State::GLState {
|
||||
m_xfbStrides.clear();
|
||||
m_xfbBufferMode = m_requestedXfbBufferMode;
|
||||
m_xfbVaryingNameMaxLength = 0;
|
||||
m_xfbNeedsScatteredCapture = false;
|
||||
m_xfbPackedStride = 0;
|
||||
if (m_requestedXfbVaryings.empty()) {
|
||||
return true;
|
||||
}
|
||||
@@ -243,8 +248,27 @@ namespace MobileGL::MG_State::GLState {
|
||||
|
||||
const Bool interleaved = m_xfbBufferMode == GL_INTERLEAVED_ATTRIBS;
|
||||
Uint32 interleavedOffset = 0;
|
||||
// ARB_transform_feedback3 lets an interleaved capture leave holes (gl_SkipComponents1..4)
|
||||
// and move on to the next buffer (gl_NextBuffer). Both only affect where the following
|
||||
// varyings land, so they are consumed here and never become XfbVaryings of their own -
|
||||
// which also keeps them out of the name list a backend declares on its own driver.
|
||||
Uint32 interleavedBufferIndex = 0;
|
||||
Vector<Uint32> interleavedStrides;
|
||||
for (SizeT i = 0; i < m_requestedXfbVaryings.size(); ++i) {
|
||||
const String& name = m_requestedXfbVaryings[i];
|
||||
if (interleaved && name == "gl_NextBuffer") {
|
||||
interleavedStrides.push_back(interleavedOffset);
|
||||
interleavedOffset = 0;
|
||||
++interleavedBufferIndex;
|
||||
m_xfbNeedsScatteredCapture = true;
|
||||
continue;
|
||||
}
|
||||
if (interleaved && name.size() == 18 && name.compare(0, 17, "gl_SkipComponents") == 0 &&
|
||||
name[17] >= '1' && name[17] <= '4') {
|
||||
interleavedOffset += static_cast<Uint32>(name[17] - '0') * 4;
|
||||
m_xfbNeedsScatteredCapture = true;
|
||||
continue;
|
||||
}
|
||||
for (SizeT j = 0; j < i; ++j) {
|
||||
if (m_requestedXfbVaryings[j] == name) {
|
||||
m_infoLog = "Transform feedback varying '" + name + "' is specified more than once.";
|
||||
@@ -285,12 +309,14 @@ namespace MobileGL::MG_State::GLState {
|
||||
}
|
||||
|
||||
varying.byteSize = bytesPerElement * static_cast<Uint32>(varying.size);
|
||||
varying.packedOffsetBytes = m_xfbPackedStride;
|
||||
m_xfbPackedStride += varying.byteSize;
|
||||
if (interleaved) {
|
||||
varying.bufferIndex = 0;
|
||||
varying.bufferIndex = interleavedBufferIndex;
|
||||
varying.offsetBytes = interleavedOffset;
|
||||
interleavedOffset += varying.byteSize;
|
||||
} else {
|
||||
varying.bufferIndex = static_cast<Uint32>(i);
|
||||
varying.bufferIndex = static_cast<Uint32>(m_xfbVaryings.size());
|
||||
varying.offsetBytes = 0;
|
||||
}
|
||||
m_xfbVaryingNameMaxLength =
|
||||
@@ -301,13 +327,22 @@ namespace MobileGL::MG_State::GLState {
|
||||
constexpr Uint32 kMaxSeparateAttribs = 4;
|
||||
constexpr Uint32 kMaxSeparateComponents = 4;
|
||||
constexpr Uint32 kMaxInterleavedComponents = 64;
|
||||
constexpr Uint32 kMaxTransformFeedbackBuffers = 4;
|
||||
if (interleaved) {
|
||||
if (interleavedOffset > kMaxInterleavedComponents * 4) {
|
||||
m_infoLog = "Transform feedback interleaved capture exceeds "
|
||||
"GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS.";
|
||||
interleavedStrides.push_back(interleavedOffset);
|
||||
if (interleavedStrides.size() > kMaxTransformFeedbackBuffers) {
|
||||
m_infoLog = "Transform feedback capture uses more buffers than "
|
||||
"GL_MAX_TRANSFORM_FEEDBACK_BUFFERS.";
|
||||
return false;
|
||||
}
|
||||
m_xfbStrides.assign(1, interleavedOffset);
|
||||
for (const Uint32 stride : interleavedStrides) {
|
||||
if (stride > kMaxInterleavedComponents * 4) {
|
||||
m_infoLog = "Transform feedback interleaved capture exceeds "
|
||||
"GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
m_xfbStrides = Move(interleavedStrides);
|
||||
} else {
|
||||
if (m_xfbVaryings.size() > kMaxSeparateAttribs) {
|
||||
m_infoLog = "Transform feedback separate capture exceeds "
|
||||
@@ -542,6 +577,20 @@ namespace MobileGL::MG_State::GLState {
|
||||
return;
|
||||
}
|
||||
|
||||
// GL_GEOMETRY_INPUT_TYPE. A draw's primitive type has to be compatible with it
|
||||
// (GL 4.6 core 11.3.1), so it is resolved for every link, not only a capturing one.
|
||||
m_gsInputPrimitive = GL_NONE;
|
||||
if (const glslang::TIntermediate* gs = m_program->getIntermediate(EShLangGeometry)) {
|
||||
switch (gs->getInputPrimitive()) {
|
||||
case glslang::ElgPoints: m_gsInputPrimitive = GL_POINTS; break;
|
||||
case glslang::ElgLines: m_gsInputPrimitive = GL_LINES; break;
|
||||
case glslang::ElgLinesAdjacency: m_gsInputPrimitive = GL_LINES_ADJACENCY; break;
|
||||
case glslang::ElgTriangles: m_gsInputPrimitive = GL_TRIANGLES; break;
|
||||
case glslang::ElgTrianglesAdjacency: m_gsInputPrimitive = GL_TRIANGLES_ADJACENCY; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
MGLOG_D("ProgramObject %u: Starting reflection", m_externalIndex);
|
||||
DoReflection();
|
||||
MGLOG_D("ProgramObject %u: Reflection done (linkStatus=%d)", m_externalIndex, (int)m_linkStatus);
|
||||
|
||||
@@ -377,6 +377,17 @@ namespace MobileGL::MG_State::GLState {
|
||||
|
||||
Bool GetDeleteStatus() const { return m_deleteStatus; }
|
||||
Bool GetLinkStatus() const { return m_linkStatus; }
|
||||
// GL_PROGRAM_BINARY_RETRIEVABLE_HINT. MobileGL exposes no program binary format
|
||||
// (GL_NUM_PROGRAM_BINARY_FORMATS is 0), so the hint is pure state - which is all
|
||||
// ARB_get_program_binary requires of it.
|
||||
Bool GetBinaryRetrievableHint() const { return m_binaryRetrievableHint; }
|
||||
void SetBinaryRetrievableHint(Bool hint) { m_binaryRetrievableHint = hint; }
|
||||
// glProgramBinary always fails here (there is no format it could accept) and the
|
||||
// spec then requires the program's LINK_STATUS to read FALSE.
|
||||
void MarkLinkFailedByProgramBinary() {
|
||||
ResetLinkArtifacts();
|
||||
m_infoLog = "No program binary format is supported.";
|
||||
}
|
||||
Bool GetValidateStatus() const { return m_validateStatus; }
|
||||
Int GetActiveAtomicCounterCount() const { return m_program->getNumAtomicCounters(); }
|
||||
Int GetActiveAttributesCount() const { return m_program->getNumPipeInputs(); }
|
||||
@@ -474,6 +485,9 @@ namespace MobileGL::MG_State::GLState {
|
||||
Uint32 bufferIndex = 0; // capture buffer slot
|
||||
Uint32 offsetBytes = 0; // offset within the capture buffer
|
||||
Uint32 byteSize = 0; // bytes captured per vertex for this varying
|
||||
// Offset within the gap-free record a backend that cannot express the GL
|
||||
// layout captures into; see NeedsScatteredTransformFeedbackCapture.
|
||||
Uint32 packedOffsetBytes = 0;
|
||||
};
|
||||
void SetTransformFeedbackVaryings(Vector<String>&& names, GLenum bufferMode) {
|
||||
m_requestedXfbVaryings = Move(names);
|
||||
@@ -491,12 +505,25 @@ namespace MobileGL::MG_State::GLState {
|
||||
}
|
||||
SizeT GetTransformFeedbackBufferCount() const { return m_xfbStrides.size(); }
|
||||
Int GetTransformFeedbackVaryingMaxLength() const { return m_xfbVaryingNameMaxLength; }
|
||||
// True when the capture layout uses gl_SkipComponents / gl_NextBuffer
|
||||
// (ARB_transform_feedback3), which no ES driver can express: it can only pack every
|
||||
// captured varying into one record with no gaps. A backend that captures through
|
||||
// such a driver has to capture into scratch storage and scatter the records into the
|
||||
// application's buffers itself, using packedOffsetBytes as the source offset and
|
||||
// (bufferIndex, offsetBytes, stride) as the destination.
|
||||
Bool NeedsScatteredTransformFeedbackCapture() const { return m_xfbNeedsScatteredCapture; }
|
||||
// Bytes one gap-free captured record occupies.
|
||||
Uint32 GetTransformFeedbackPackedStride() const { return m_xfbPackedStride; }
|
||||
// True when the capture stage is a triangle-strip geometry shader with a
|
||||
// statically-known emit sequence: the Vulkan capture order then needs the GL
|
||||
// odd-triangle vertex swap after EndTransformFeedback.
|
||||
Bool HasGsTriangleStripCaptureFixup() const { return m_gsStripCaptureFixup; }
|
||||
// Triangles per strip, in emission order, for ONE geometry invocation.
|
||||
const Vector<Uint32>& GetGsStripTriangles() const { return m_gsStripTriangles; }
|
||||
// GL_GEOMETRY_INPUT_TYPE of the linked geometry stage (GL_POINTS, GL_LINES,
|
||||
// GL_LINES_ADJACENCY, GL_TRIANGLES or GL_TRIANGLES_ADJACENCY), or GL_NONE when the
|
||||
// program has no geometry stage. Draws must present a compatible primitive type.
|
||||
GLenum GetGeometryInputType() const { return m_gsInputPrimitive; }
|
||||
|
||||
Uint GetExternalIndex() const { return m_externalIndex; }
|
||||
// Globally-unique, never-reused id for this program object's lifetime. Unlike the GL
|
||||
@@ -577,6 +604,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
String m_infoLog;
|
||||
Bool m_deleteStatus = false;
|
||||
Bool m_linkStatus = false;
|
||||
Bool m_binaryRetrievableHint = false;
|
||||
Bool m_validateStatus = true;
|
||||
Uint32 m_backendStateVersion = 0;
|
||||
|
||||
@@ -604,7 +632,10 @@ namespace MobileGL::MG_State::GLState {
|
||||
Vector<Uint32> m_xfbStrides;
|
||||
Vector<Uint32> m_gsStripTriangles;
|
||||
Bool m_gsStripCaptureFixup = false;
|
||||
GLenum m_gsInputPrimitive = GL_NONE;
|
||||
GLenum m_xfbBufferMode = GL_INTERLEAVED_ATTRIBS;
|
||||
Int m_xfbVaryingNameMaxLength = 0;
|
||||
Bool m_xfbNeedsScatteredCapture = false;
|
||||
Uint32 m_xfbPackedStride = 0;
|
||||
};
|
||||
} // namespace MobileGL::MG_State::GLState
|
||||
|
||||
@@ -154,6 +154,17 @@ namespace MobileGL {
|
||||
return m_parameters.PointSize;
|
||||
}
|
||||
|
||||
void RenderState::SetPatchVertices(Uint vertices) {
|
||||
if (m_parameters.PatchVertices == vertices) return;
|
||||
|
||||
m_parameters.PatchVertices = vertices;
|
||||
++m_version;
|
||||
}
|
||||
|
||||
Uint RenderState::GetPatchVertices() const {
|
||||
return m_parameters.PatchVertices;
|
||||
}
|
||||
|
||||
void RenderState::SetPolygonOffset(Float factor, Float units) {
|
||||
if (m_parameters.PolygonOffsetFactor == factor && m_parameters.PolygonOffsetUnits == units) return;
|
||||
|
||||
|
||||
@@ -224,6 +224,8 @@ namespace MobileGL {
|
||||
IntVec4 Viewport = IntVec4(0, 0, 0, 0); // x, y, width, height
|
||||
Float LineWidth = 1.0f;
|
||||
Float PointSize = 1.0f;
|
||||
// GL_PATCH_VERTICES: how many vertices one tessellation patch consumes.
|
||||
Uint PatchVertices = 3;
|
||||
Float PolygonOffsetFactor = 0.0f;
|
||||
Float PolygonOffsetUnits = 0.0f;
|
||||
|
||||
@@ -319,6 +321,8 @@ namespace MobileGL {
|
||||
Float GetLineWidth() const;
|
||||
void SetPointSize(Float size);
|
||||
Float GetPointSize() const;
|
||||
void SetPatchVertices(Uint vertices);
|
||||
Uint GetPatchVertices() const;
|
||||
void SetPolygonOffset(Float factor, Float units);
|
||||
Float GetPolygonOffsetFactor() const;
|
||||
Float GetPolygonOffsetUnits() const;
|
||||
|
||||
@@ -24,6 +24,19 @@ namespace MobileGL {
|
||||
TextureObjectBase::TextureObjectBase(TextureTarget target, Uint externalIndex)
|
||||
: m_externalIndex(externalIndex), m_lifetimeId(AllocateLifetimeId()), m_target(target) {
|
||||
m_sampler = MakeShared<SamplerObject>(0);
|
||||
if (target == TextureTarget::TextureRectangle) {
|
||||
// A rectangle texture has no mip chain, so its initial sampler state is not
|
||||
// the shared one: TEXTURE_MIN_FILTER is LINEAR and TEXTURE_WRAP_S/T are
|
||||
// CLAMP_TO_EDGE (GL 4.6 core table 23.15). Leaving the 2D default of
|
||||
// NEAREST_MIPMAP_LINEAR in place makes the texture mipmap-incomplete from
|
||||
// birth, and every lookup that the application never re-filtered reads
|
||||
// (0, 0, 0, 1) instead of its contents.
|
||||
m_sampler->SetMinFilter(SamplerFilterMode::Linear);
|
||||
m_sampler->SetMipmapMode(SamplerMipmapMode::None);
|
||||
m_sampler->SetWrapS(SamplerWrapMode::ClampToEdge);
|
||||
m_sampler->SetWrapT(SamplerWrapMode::ClampToEdge);
|
||||
m_sampler->SetWrapR(SamplerWrapMode::ClampToEdge);
|
||||
}
|
||||
}
|
||||
|
||||
TextureInternalFormat TextureObjectBase::GetFormat() const {
|
||||
@@ -335,6 +348,58 @@ namespace MobileGL {
|
||||
|
||||
// TODO: add other texture types as needed
|
||||
|
||||
Bool IsMipmapCompleteForFilter(const ITextureObject* texture, Bool mipmapped) {
|
||||
if (texture == nullptr) return true;
|
||||
if (!texture->IsComplete()) return false;
|
||||
if (!mipmapped) return true;
|
||||
|
||||
const auto* mipmapTexture = AsMipmapTexture(texture);
|
||||
if (mipmapTexture == nullptr) return true; // no mip chain to be incomplete about
|
||||
|
||||
const UintVec2& levelRange = texture->GetLevelRange();
|
||||
const Uint baseLevel = levelRange.x();
|
||||
const Uint storedLevels = mipmapTexture->GetMipmapLevelCount();
|
||||
if (baseLevel >= storedLevels) return false;
|
||||
|
||||
// An array texture's layer count is not a dimension of the image: it stays put all
|
||||
// the way down the chain (GL 4.6 core 8.14.3). GetMipmapTexelSize reports it in the
|
||||
// slot after the image's own dimensions.
|
||||
const TextureTarget target = texture->GetTarget();
|
||||
Int shrinkingComponents = 3;
|
||||
if (target == TextureTarget::Texture1DArray) {
|
||||
shrinkingComponents = 1;
|
||||
} else if (target == TextureTarget::Texture2DArray || target == TextureTarget::TextureCubeMapArray) {
|
||||
shrinkingComponents = 2;
|
||||
}
|
||||
|
||||
for (const auto uploadTarget : texture->GetUploadTargets()) {
|
||||
const IntVec3 baseSize = mipmapTexture->GetMipmapTexelSize(uploadTarget, baseLevel);
|
||||
Int largest = 0;
|
||||
for (Int component = 0; component < shrinkingComponents; ++component) {
|
||||
largest = std::max(largest, baseSize[component]);
|
||||
}
|
||||
if (largest <= 0) return false;
|
||||
|
||||
// p = log2 of the largest base dimension: the last level the chain needs
|
||||
// before every dimension has reached 1. TEXTURE_MAX_LEVEL can cut it short.
|
||||
Uint p = 0;
|
||||
for (Int extent = largest; extent > 1; extent >>= 1) ++p;
|
||||
const Uint lastLevel = std::min(baseLevel + p, levelRange.y());
|
||||
|
||||
for (Uint level = baseLevel; level <= lastLevel; ++level) {
|
||||
if (level >= storedLevels) return false;
|
||||
const IntVec3 actual = mipmapTexture->GetMipmapTexelSize(uploadTarget, level);
|
||||
for (Int component = 0; component < 3; ++component) {
|
||||
const Int expected = component < shrinkingComponents
|
||||
? std::max(1, baseSize[component] >> (level - baseLevel))
|
||||
: baseSize[component];
|
||||
if (actual[component] != expected) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace GLState
|
||||
} // namespace MG_State
|
||||
} // namespace MobileGL
|
||||
|
||||
@@ -156,6 +156,14 @@ namespace MobileGL::MG_State::GLState {
|
||||
? static_cast<TextureObjectMipmap*>(texture)
|
||||
: nullptr;
|
||||
}
|
||||
// Whether the texture satisfies the mipmap-completeness rules a minification filter
|
||||
// that samples the mip chain imposes (GL 4.6 core 8.17): every level from the base to
|
||||
// the effective max must exist at exactly half the previous one's size. `mipmapped` is
|
||||
// the effective sampler's answer to "does this filter read more than the base level" -
|
||||
// when it is false only base-level completeness matters, which the ordinary
|
||||
// IsComplete() already covers. Sampling an incomplete texture returns (0, 0, 0, 1).
|
||||
Bool IsMipmapCompleteForFilter(const ITextureObject* texture, Bool mipmapped);
|
||||
|
||||
inline const TextureObjectMipmap* AsMipmapTexture(const ITextureObject* texture) {
|
||||
return (texture && texture->GetStorageType() == TextureStorageType::Mipmap)
|
||||
? static_cast<const TextureObjectMipmap*>(texture)
|
||||
|
||||
@@ -916,6 +916,12 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
GLfloat minFragmentInterpolationOffset = -0.5f;
|
||||
GLfloat maxFragmentInterpolationOffset = 0.4375f;
|
||||
GLint fragmentInterpolationOffsetBits = 4;
|
||||
// Core minimums of both APIs (GL 4.6 table 23.53, ES 3.1 table 20.40); the probe
|
||||
// below only ever widens them.
|
||||
GLint minProgramTextureGatherOffset = -8;
|
||||
GLint maxProgramTextureGatherOffset = 7;
|
||||
GLint maxPatchVertices = 32;
|
||||
GLint maxTessGenLevel = 64;
|
||||
glesFuncs.glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, aliasedLineWidthRange);
|
||||
glesFuncs.glGetFloatv(GL_SMOOTH_LINE_WIDTH_RANGE, smoothLineWidthRange);
|
||||
glesFuncs.glGetFloatv(GL_SMOOTH_LINE_WIDTH_GRANULARITY, &smoothLineWidthGranularity);
|
||||
@@ -944,6 +950,14 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
// single test case. 1 is a spec-legal value (the minimum required), so cap
|
||||
// to what is actually implemented instead of forwarding the raw driver limit.
|
||||
maxSampleMaskWords = std::min(maxSampleMaskWords, 1);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_PATCH_VERTICES, &maxPatchVertices);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_TESS_GEN_LEVEL, &maxTessGenLevel);
|
||||
glesFuncs.glGetIntegerv(GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET, &minProgramTextureGatherOffset);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET, &maxProgramTextureGatherOffset);
|
||||
// A driver that leaves the probe untouched (pre-ES 3.1, or an ignored enum) must not
|
||||
// drag the advertised range below what GL 4.0 requires of us.
|
||||
minProgramTextureGatherOffset = std::min(minProgramTextureGatherOffset, -8);
|
||||
maxProgramTextureGatherOffset = std::max(maxProgramTextureGatherOffset, 7);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureImageUnits);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxVertexTextureImageUnits);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS, &maxComputeTextureImageUnits);
|
||||
@@ -1027,6 +1041,10 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
caps.MaxIntegerSamples = maxIntegerSamples;
|
||||
caps.MaxSamples = maxSamples;
|
||||
caps.MaxSampleMaskWords = maxSampleMaskWords;
|
||||
caps.MaxPatchVertices = maxPatchVertices;
|
||||
caps.MaxTessGenLevel = maxTessGenLevel;
|
||||
caps.MinProgramTextureGatherOffset = minProgramTextureGatherOffset;
|
||||
caps.MaxProgramTextureGatherOffset = maxProgramTextureGatherOffset;
|
||||
caps.MaxTextureImageUnits = maxTextureImageUnits;
|
||||
caps.MaxVertexTextureImageUnits = maxVertexTextureImageUnits;
|
||||
caps.MaxComputeTextureImageUnits = maxComputeTextureImageUnits;
|
||||
|
||||
@@ -1102,6 +1102,10 @@ namespace MobileGL {
|
||||
Int MaxIntegerSamples = 1;
|
||||
Int MaxSamples = 1;
|
||||
Int MaxSampleMaskWords = 1;
|
||||
Int MaxPatchVertices = 32;
|
||||
Int MaxTessGenLevel = 64;
|
||||
Int MinProgramTextureGatherOffset = -8;
|
||||
Int MaxProgramTextureGatherOffset = 7;
|
||||
Int MaxTextureImageUnits = 32;
|
||||
Int MaxVertexTextureImageUnits = 32;
|
||||
Int MaxComputeTextureImageUnits = 32;
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace MobileGL {
|
||||
}
|
||||
}
|
||||
|
||||
GLbitfield ConvertBufferMappingAccessToGLEnum(BufferMappingAccessBit access) {
|
||||
GLbitfield ConvertBufferMappingAccessToGLEnum(Flags<BufferMappingAccessBit> access) {
|
||||
GLbitfield result = 0;
|
||||
if (access & BufferMappingAccessBit::Read) result |= GL_MAP_READ_BIT;
|
||||
if (access & BufferMappingAccessBit::Write) result |= GL_MAP_WRITE_BIT;
|
||||
|
||||
@@ -14,6 +14,6 @@ namespace MobileGL {
|
||||
namespace MG_Util {
|
||||
GLenum ConvertBufferTargetToGLEnum(BufferTarget bufferTarget);
|
||||
GLenum ConvertBufferUsageToGLEnum(BufferUsage usage);
|
||||
GLbitfield ConvertBufferMappingAccessToGLEnum(BufferMappingAccessBit access);
|
||||
GLbitfield ConvertBufferMappingAccessToGLEnum(Flags<BufferMappingAccessBit> access);
|
||||
} // namespace MG_Util
|
||||
} // namespace MobileGL
|
||||
@@ -397,11 +397,19 @@ namespace MobileGL {
|
||||
}
|
||||
} else {
|
||||
switch (opcode) {
|
||||
// Everything that takes normalized coordinates. Tracing each one back to
|
||||
// its image type would let a module mix a normalized 2D lookup with a
|
||||
// rectangle fetch, but the extra reach is not worth the risk of getting
|
||||
// the trace wrong: decline the whole module instead.
|
||||
case spv::Op::OpImageSampleImplicitLod:
|
||||
// Normalized-coordinate lookups whose ESSL form the backend's
|
||||
// NormalizeRectSamplerCoordinates post-pass cannot repair: the
|
||||
// coordinate is either fused with something else in a single argument
|
||||
// (the Dref sample forms carry the compare value in coord.z) or the
|
||||
// divide would have to happen after a projective divide. Tracing each
|
||||
// one back to its image type would let a module mix a normalized 2D
|
||||
// lookup with a rectangle fetch, but the extra reach is not worth the
|
||||
// risk of getting the trace wrong: decline the whole module instead.
|
||||
//
|
||||
// OpImageSampleImplicitLod, OpImageGather and OpImageDrefGather are
|
||||
// absent because all three become an ESSL call whose argument 1 is the
|
||||
// bare texel-space coordinate, which the post-pass divides by the
|
||||
// texture size.
|
||||
case spv::Op::OpImageSampleExplicitLod:
|
||||
case spv::Op::OpImageSampleDrefImplicitLod:
|
||||
case spv::Op::OpImageSampleDrefExplicitLod:
|
||||
@@ -409,8 +417,6 @@ namespace MobileGL {
|
||||
case spv::Op::OpImageSampleProjExplicitLod:
|
||||
case spv::Op::OpImageSampleProjDrefImplicitLod:
|
||||
case spv::Op::OpImageSampleProjDrefExplicitLod:
|
||||
case spv::Op::OpImageGather:
|
||||
case spv::Op::OpImageDrefGather:
|
||||
case spv::Op::OpImageSparseSampleImplicitLod:
|
||||
case spv::Op::OpImageSparseSampleExplicitLod:
|
||||
case spv::Op::OpImageSparseSampleDrefImplicitLod:
|
||||
|
||||
Reference in New Issue
Block a user