Compare commits

..
19 Commits
Author SHA1 Message Date
BZLZHH 28c3cfc1d6 [Fix] (DirectVulkan, MG_State): make a shader-written storage buffer readable on Magma
Reading a buffer a compute shader wrote gave zeros: the frontend shadow that MapBuffer
resolves against is only maintained by uploads, and Magma had no path back. Every
KHR-GL40.texture_gather case ends by dispatching a compute shader into an SSBO and
comparing the mapped result, so 66 of 75 failed on it.

Magma needs no readback: EnsureGpuResidentStorage - the same host-visible coherent
adoption the transform feedback capture already uses - makes the shadow BE the memory
the shader writes, so binding a buffer as a shader storage buffer now adopts it. What
coherence does not give is ordering: the writes are visible once they have happened,
and the CPU was reading before the dispatch had retired. The readback op therefore
submits the recorded work and waits.

That exposed a mistake in the frontend flag this rides on: MarkGpuWritten skipped
GPU-resident buffers, reasoning there was no shadow to refresh. True, but the wait is
still needed - "reconcile with the GPU write" is not always "copy it back", and which
of the two it is belongs to the backend. The flag now only says a write is outstanding;
DirectGLES's readback still skips its persistent-mapped buffers when copying.

KHR-GL40.texture_gather on Magma: 66 failures -> 19 (the rest are rectangle textures,
mipmap completeness and tessellation, all still to do). Espryt stays at 75/75.
2026-08-04 11:55:17 -04:00
BZLZHH 38e04eefae [Fix] (DirectVulkan): size the indirect draw command by GL's struct, not the renderer's
The indirect draw paths bounded their read out of GL_DRAW_INDIRECT_BUFFER - and took
their default stride - from `sizeof(DrawCmdParam)`, this renderer's own draw-parameter
struct. That is not the command GL defines: DrawCmdParam carries two extra members for
bounding vertex-stream conversion and is 24 bytes, where GL's DrawArraysIndirectCommand
is four uint32.

So every glDrawArraysIndirect against a tightly-sized indirect buffer - which is what an
application writes, and what the CTS writes - failed the range check and drew nothing.
It went unnoticed on the elements side only by coincidence: DrawIndexedCmdParam happens
to be exactly the 20 bytes of DrawElementsIndirectCommand.

Both sizes are now named constants of GL's own layout.

KHR-GL40.draw_indirect on Magma: 21 failures -> 3.
2026-08-04 11:47:35 -04:00
BZLZHH fd29cb914e [Fix] (DirectVulkan, MG_State): give each transform feedback object its own capture counters
The frontend half of ARB_transform_feedback2 landed for both backends, but Magma's
capture was still written for the one implicit span GL 3.3 has:

- A paused span kept capturing. VK_EXT_transform_feedback's counter buffers already
  make consecutive draws append, so pausing is simply "do not wrap this draw" - the
  counters keep their values and the next resumed draw carries on where the last
  captured one stopped.
- Those counter buffers were context-wide. Transform feedback objects can each hold an
  open, paused span at the same time - KHR-GL40.transform_feedback.draw_xfb_test keeps
  three - and they were all appending through one set of four slots. Each object now
  gets its own group, handed out on first use; past sixteen objects they share group 0,
  which only matters for concurrently-paused spans.
- The generation that identifies a span is what a backend keys its append state on, so
  it is now part of the per-object state the frontend saves and restores. Without that,
  resuming an object that was paused before another one began looked like a new span
  and restarted its counters at zero.

GL_PRIMITIVES_GENERATED needed one more thing. It counts what the last vertex
processing stage emitted whether or not anything is being captured, but
VK_QUERY_TYPE_TRANSFORM_FEEDBACK_STREAM_EXT only counts what the capture saw - so a
draw made while the span was paused is invisible to it. The frontend now tallies those
draws, and the Vulkan query adds the delta at result time. The correction lives in the
backend that needs it: an ES driver's GL_PRIMITIVES_GENERATED counts them by itself, and
adding it there too would double them.

transform_feedback* on Magma: 4 failures -> 3. Espryt stays at 38/38.
2026-08-04 11:42:44 -04:00
BZLZHH 38497174c8 [Feat] (MG_Impl): implement the double-precision uniform state
glUniform*d, glUniformMatrix*dv, their glProgramUniform twins and glGetUniformdv were
all stubs - 35 entry points - so a GL 4.0 program's double uniforms could be declared
and located but never set or read. Worse, glGetUniformfv on one did reach the storage:
the generic getter memcpy'd the uniform's declared size into the caller's buffer, so a
4-byte float pointer received 8 bytes. That overrun is what took the process down in
KHR-GL40.gpu_shader_fp64.fp64.state_query.

The upload path is already templated on the component type, so the vector forms are
wiring. A matrix is not: the column stride the linker used for a double matrix is not
the 16 bytes a float one gets. It is not guessed - the slot the uniform was given is
exactly `columns` columns wide, so dividing states the stride the rest of the pipeline
already agreed on, for both the upload and the readback.

The four getters now convert instead of reinterpreting when the uniform holds doubles,
following GL 4.6 core 7.6: round to nearest for the integer queries, and clamp into the
queried type's range so a negative double read through glGetUniformuiv is 0 rather than
its two's complement.

The case still fails one step further on, where it queries the same uniforms through
GL_ARB_program_interface_query: those calls are answered by the backend program, and an
fp64 shader has none - ESSL has no doubles, so it never links. Answering them from the
frontend reflection is a separate change.
2026-08-04 11:06:15 -04:00
BZLZHH b95fcb7bca [Feat] (MG_State, MG_Impl, DirectGLES): implement glPatchParameteri
GL_PATCH_VERTICES decides how many vertices one tessellation patch consumes, and
glPatchParameteri was a stub - so the value stayed at the driver's default of 3 no
matter what the application asked for. KHR-GL40.texture_gather.gather-tesselation-shader
sets it to 1 and then draws a single patch: with the request dropped the draw had too
few vertices for one patch, produced nothing at all, and the case read back the clear
colour.

The value is context state on both sides and ES 3.2 spells the entry point exactly the
same way, so it is stored in the render state (where glGetIntegerv(GL_PATCH_VERTICES)
now finds it) and forwarded. Validation needs the real bound, so GL_MAX_PATCH_VERTICES
and GL_MAX_TESS_GEN_LEVEL are probed off the host driver alongside the other limits and
answered from there too; the defaults are the GL 4.0 core minimums.

KHR-GL40.texture_gather is now 75/75.
2026-08-04 10:58:14 -04:00
BZLZHH 5fce287de5 [Fix] (DirectGLES): generate a three-channel float mip chain on the CPU
glGenerateMipmap requires the level-0 format to be colour-renderable, and ES has no
colour-renderable three-channel float format at all - so an ES driver rejects
GL_RGB16F and GL_RGB32F where every desktop driver accepts them, and the error was
forwarded to the application. KHR-GL40.texture_gather.plain-gather-float-2d-rgb and
its offset- sibling build their texture that way and fail on the leftover error alone.

The blit-based emulation already used for GL_R11F_G11F_B10F is no help: it renders
level n from level n-1, so it needs exactly the renderability that is missing. But a
format the driver cannot render into is a format nothing can have rendered into
either, which makes the frontend's own copy of the texels authoritative for precisely
these formats. So the chain is box-filtered there and the levels are marked dirty; the
backend sync that follows uploads them like any other texture data.

Deliberately narrow: only the two formats whose texels are a plain float array, and
only when they are what the texture actually holds. Every other format keeps the
driver's behaviour, error included.
2026-08-04 10:54:17 -04:00
BZLZHH ae6949e459 [Fix] (MG_State, DirectGLES): sample a mipmap-incomplete texture as black
A minification filter that reads the mip chain requires every level from the base down
to hold exactly half the previous one's size; a texture that does not is incomplete and
every lookup on it returns (0, 0, 0, 1) (GL 4.6 core 8.17). Nothing checked it.

The ES driver cannot catch this on MobileGL's behalf, which is why it has to be a
frontend rule here: the backend texture is immutable storage allocated from the level
set as it stood, so a level the application later redefined at a different size never
reaches the driver at all, and the ES texture stays complete. That is exactly what
KHR-GL40.texture_gather.incomplete-texture does - it redefines level 1 of a complete
chain as 1x1 - and it read the original contents back.

The check runs where the sampling bindings are established, and an incomplete texture
simply leaves its native target unbound: an unbound ES target samples as (0, 0, 0, 1),
which is the answer GL asks for, with no scratch texture to keep around.

An array texture's layer count is not one of the dimensions that halves, so the
comparison only shrinks the components that belong to the image itself - getting that
wrong turned eight *-2darray cases black.
2026-08-04 10:49:51 -04:00
BZLZHH 5437947240 [Feat] (DirectGLES, MG_Util): normalize the coordinates of a rectangle lookup
A rectangle texture is emulated on an ES 2D texture, and LowerRectImagesForEssl
rewrites the image type in the SPIR-V to match. That is exact only where the lookup
addresses texels directly, which is why the pass declined any module containing a
lookup that takes normalized coordinates - the whole KHR-GL40.texture_gather 2drect
set among them.

The missing half is one divide: a rectangle lookup's coordinate is in texels and the
2D lookup it becomes wants [0,1], so the coordinate has to be divided by the texture's
size. It goes in on the ESSL the transpiler produces, next to the LOD-bias emulation
that already rewrites lookup arguments there, and reads the size back with
textureSize() rather than plumbing a uniform down - the emulated texture is a real ES
2D texture, so the shader can ask it directly.

Only the forms whose argument 1 is the bare coordinate are rewritten - texture,
textureOffset and the three textureGather flavours, which covers the Dref gathers too
because those carry the compare value in a separate argument. texelFetch is
deliberately left alone: its coordinates are integer texels on both targets. The
SPIR-V pass keeps declining everything else, so a projective lookup or a Dref sample
(where the compare value rides in coord.z) still refuses the module instead of
producing something subtly wrong.

Which samplers were declared rectangle is no longer visible in the transpiled source -
they are plain sampler2D by then - so the names come from the frontend program's
reflection.
2026-08-04 10:38:01 -04:00
BZLZHH f38dbf018d [Fix] (MG_State): give a rectangle texture its own initial sampler state
Every texture object started from the shared defaults, which are the 2D ones:
TEXTURE_MIN_FILTER of NEAREST_MIPMAP_LINEAR and TEXTURE_WRAP_S/T of REPEAT. A
rectangle texture has no mip chain at all, so GL gives it a different initial state -
LINEAR and CLAMP_TO_EDGE (GL 4.6 core table 23.15) - and a mipmapped minification
filter is not even a legal value to set on one.

With the 2D default in place a rectangle texture was mipmap-incomplete the moment it
was created, and an application that (correctly) never touches the filters read
(0, 0, 0, 1) out of every lookup. That is what the eleven
KHR-GL40.texture_gather.*-2drect cases saw: they set only the wrap modes, because the
filters are already what a rectangle texture needs.
2026-08-04 10:37:46 -04:00
BZLZHH 2dcc15bb0e [Feat] (MG_Impl, DirectGLES): advertise GL_ARB_get_program_binary with no binary format
glProgramParameteri is not core before GL 4.1, so in the 4.0 context the CTS runs it
only exists through GL_ARB_get_program_binary or GL_ARB_separate_shader_objects.
MobileGL advertised neither, so dEQP's loader left the entry point null - and
KHR-GL40.api.coverage, which registers glProgramParameteri from GL 3.2 upwards, called
straight through the null pointer and took the process down.

GL_NUM_PROGRAM_BINARY_FORMATS was already 0, and the extension explicitly allows an
implementation to support no binary format at all; that is the honest state of things
here, since a MobileGL program is a glslang link plus a per-backend translation with no
serialised form. So the extension is advertised for what it really provides:
glProgramParameteri stores GL_PROGRAM_BINARY_RETRIEVABLE_HINT (reported back by
glGetProgramiv alongside a GL_PROGRAM_BINARY_LENGTH of zero), glGetProgramBinary is the
INVALID_OPERATION the spec requires when that length is zero, and glProgramBinary
rejects every format with INVALID_ENUM and leaves the program's LINK_STATUS false.

Applications that ask for a binary get the documented "no formats" answer and fall
back, which is what they already had to do - only now they can ask.
2026-08-04 10:27:23 -04:00
BZLZHH ff76af9df7 [Fix] (MG_State, MG_Impl): a transform feedback name is only an object once it is bound
glIsTransformFeedback answered GL_TRUE for any name glGenTransformFeedbacks had handed
out. A generated name is reserved but does not denote an object until the first
glBindTransformFeedback (GL 4.6 core 13.2.1) - the same rule the other object types
follow - and KHR-GL40.api.coverage checks exactly the window in between.

The two questions are now asked separately: whether a name may be bound or deleted
(reserved, which is what the delete and bind paths need) and whether it is an object
(reserved and bound at least once).
2026-08-04 10:27:23 -04:00
BZLZHH 76f37a18e6 [Feat] (MG_State, MG_Impl, DirectGLES): transform feedback objects, pause/resume and the special capture names
GL 4.0 folds ARB_transform_feedback2 and _3 into core, and neither existed:
glGenTransformFeedbacks, glBindTransformFeedback, glDeleteTransformFeedbacks,
glIsTransformFeedback, glPause/ResumeTransformFeedback, the whole
glDrawTransformFeedback family and glBegin/EndQueryIndexed were all stubs, and
gl_NextBuffer / gl_SkipComponents1..4 failed the link as "not an output of the vertex
stage". Seven KHR-GL40.transform_feedback* cases failed on it, three of them by
leaving a capture open at deinit and taking the process down.

Objects. The capture state and the indexed GL_TRANSFORM_FEEDBACK_BUFFER bindings are
object state, but the context keeps one live copy of both, which is what every
existing reader - each backend's per-draw sync, the drawing and getter paths - is
written against. Rather than teach all of them about objects, a bind saves the live
copy into the outgoing object and restores the incoming one's. Object 0 is the
default object and needs no seeding; operator[] materialises the rest on first touch.

Pause. A paused span captures nothing, and three rules key off that: a draw is exempt
from the capture primitive-mode match, it feeds PRIMITIVES_GENERATED but not
TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, and glUseProgram is allowed again (that last
one was already refused for an active capture, correctly for GL 3.3, which has no
pause).

glDrawTransformFeedback replays the vertices the object captured in its last completed
span, recorded at End. "Has a completed span" is tracked separately from that count,
because a completed empty span draws nothing while an object that never ended one is
INVALID_OPERATION. Drawing from the object whose capture is currently open is
deliberately allowed - feeding a result straight into the next span is the point of
KHR-GL40.transform_feedback.draw_xfb_feedbackk_test.

DirectGLES gets a real driver object per frontend object. That is the only reason the
default one would not do: several objects can be paused at once, and a paused span
lives inside the driver's object. The deferred driver-side Begin (still needed - ES
wants the program current and the buffers bound) now also has to be held back while
the span is paused, or a pause taken before the first draw would open the span on that
draw and subject it to the primitive-mode rule it is exempt from.

Special names. gl_NextBuffer and gl_SkipComponents<n> are consumed during varying
resolution and never become varyings of their own, so they only move where the
following ones land - and stay out of the name list the backend declares on its own
driver. ES cannot express the resulting layout at all: it packs every captured varying
into one gap-free record. So when the layout has holes or spans several buffers,
DirectGLES captures into a scratch buffer bound in place of the application's, and
End distributes the records to the offsets GL asked for. Only the bytes a varying
occupies are written, which is exactly what makes the holes keep the contents the
application left there - the property KHR-GL40.transform_feedback3.skip_components
checks.

glBegin/EndQueryIndexed and glGetQueryIndexediv differ from the plain forms only in the
vertex stream they address, so they validate the index and forward. GL_MAX_VERTEX_STREAMS
stays at 1: multi-stream capture needs ARB_gpu_shader5 stream qualifiers that no ES
driver implements, and the CTS cases that need more than one stream check the limit and
skip.

KHR-GL40.transform_feedback, transform_feedback2 and transform_feedback3: 38/38.
2026-08-04 10:18:53 -04:00
BZLZHH 8d1a734c22 [Fix] (MG_State, MG_Impl): reject a draw mode the geometry stage cannot accept
A geometry shader declares the primitive type it consumes, and a draw may only present
a mode that decomposes into it - points for `points`, the three triangle modes for
`triangles`, and so on (GL 4.6 core 11.3.1). Anything else is GL_INVALID_OPERATION.
Nothing checked it, so KHR-GL40.draw_indirect.negative-gshIncompatible-arrays and
-elements drew points through a `layout(triangles) in` shader and got no error.

The program object had no notion of the geometry input primitive at all: glslang knows
it right after the link, so it is read off the geometry intermediate and kept as the
GL enum (this is also what GL_GEOMETRY_INPUT_TYPE would report). Resolved on every
link rather than only when transform feedback captures the stage, since every draw
consults it, and cleared with the rest of the link artifacts.

The check sits on the shared pre-draw gate next to the transform feedback primitive
rule, which is the same shape of constraint. GL_PATCHES is deliberately exempt: it is
the tessellation pipeline's input and has already become the tessellator's output
primitive by the time the geometry stage sees it.

draw_indirect is now at 70/70.
2026-08-04 09:55:36 -04:00
BZLZHH 7d215028fb [Fix] (MG_Impl): validate the draw mode and the indirect draw's command source
Two classes of draw-time error were never raised, which the KHR-GL40.draw_indirect
negative-* cases check one by one:

- `mode` was passed through unexamined, so glDrawArraysIndirect(GL_FLOAT, ...) reached
  the backend instead of raising GL_INVALID_ENUM. The check belongs on the shared
  pre-draw gate, so it now covers every draw entry point rather than just the indirect
  pair. Nothing that used to render stops rendering: a mode the frontend now rejects is
  a mode the backend driver was rejecting anyway, silently.
- The indirect commands read their arguments out of the buffer bound to
  GL_DRAW_INDIRECT_BUFFER, and all three of that source's preconditions were unchecked
  (GL 4.6 core 10.3.10): a 4-byte-aligned offset, a bound buffer at all, and enough room
  left in it for the whole 16- or 20-byte command. glDrawElementsIndirect also never
  validated its index type, which is the same accepted set as the rest of the
  DrawElements family.

Takes the group from 24 failures to 2 - both of the remaining ones are the geometry
shader input-primitive compatibility rule, which needs reflection the program object
does not keep yet.
2026-08-04 09:52:40 -04:00
BZLZHH 00534d8bbc [Fix] (MG_Impl): report the draw-indirect binding and the buffer access state
Three pieces of queryable buffer state were missing, all of them read by the
KHR-GL40.draw_indirect basic-binding-* and basic-buffer-* cases:

- GL_DRAW_INDIRECT_BUFFER_BINDING had no case in glGetIntegerv, so it raised
  GL_INVALID_ENUM and left the caller's variable untouched (the test read back its own
  -9999 sentinel). GL_DISPATCH_INDIRECT_BUFFER_BINDING right next to it was already
  handled; this is the same two lines against BufferTarget::DrawIndirect. Because
  glGetBooleanv/glGetFloatv/glGetDoublev all widen from the integer path, one case
  fixes all four getters.
- GL_BUFFER_ACCESS answered 0 for an unmapped buffer. Its initial value is
  GL_READ_WRITE and glUnmapBuffer restores it (GL 4.6 core table 6.2); 0 is not a legal
  value of that state at all, and the test threw on the unrecognised enum.
- GL_BUFFER_ACCESS_FLAGS was not implemented, so it fell through to the invalid-pname
  arm. It is the MapBufferRange bitfield verbatim, which the mapping access flags
  already hold in normalised form - glMapBuffer's access enum is converted on the way
  in - so it converts straight back out, and reads zero while unmapped.
2026-08-04 09:52:40 -04:00
BZLZHH d81a6a0998 [Fix] (MG_Impl): silently ignore program and shader name zero on delete
glDeleteProgram and glDeleteShader are the two entry points in the program/shader name
space where 0 is not "a name GL never handed out" but an explicit no-op: "if program is
zero, it is silently ignored" (GL 4.6 core 7.3, and 7.1 for shaders). Both went through
the shared name validator instead and recorded GL_INVALID_VALUE.

Only tests that never got as far as creating a program noticed, because they still run
their cleanup path: the five KHR-GL40.texture_gather.*-cube-array cases bail out of
Init with "GL_ARB_texture_cube_map_array not supported", then Cleanup deletes its
zero-initialised handles and the leftover error fails the case after the fact - the
downstream-error-misattribution shape. Every array-taking delete already skipped 0.
2026-08-04 09:52:40 -04:00
BZLZHH 9bf23d7ffd [Fix] (MG_State, DirectGLES): read a shader-written storage buffer back before mapping it
Buffer contents live in a CPU shadow that every read - MapBuffer, MapBufferRange,
GetBufferSubData, CopyBufferSubData - resolves against, and backend transfer ops only
ever push the shadow outwards. Two paths already knew the GPU can write a buffer on
its own and mirrored the result back by hand (ReadPixels into a pixel-pack buffer,
the transform feedback capture at EndTransformFeedback); a shader storage buffer
written by a draw or a dispatch had no such path at all, so the map handed the
application the bytes from before the dispatch.

Nothing exercised it until now because GL 3.3 has no compute stage. Every
KHR-GL40.texture_gather case ends by dispatching a compute shader that writes its
sampled texel into an SSBO and comparing the mapped result, and all 71 read back the
zero-filled shadow.

Adds the missing direction as a backend op: BufferObject::MarkGpuWritten flags a
buffer the GPU may have moved ahead of the shadow, SyncGpuWrites pulls it back at
every read point, and DirectGLES implements the readback with a plain read map of the
ES buffer. The flag is raised where the storage-buffer points are bound for the
upcoming draw or dispatch, which is the last moment the set of exposed buffers is
known, and cleared by the readback - so a buffer nothing writes costs one bool test
per map. Backends that cannot read their storage back leave the op null and keep
today's behaviour; a GPU-resident (coherent persistent) buffer needs nothing, since
its reads already resolve against the memory the shader wrote.

Drops the texture_gather failures from 71/75 to 25/75 with no crashes left.
2026-08-04 09:40:53 -04:00
BZLZHH 41e45f7d48 [Fix] (MG_Impl): let an indexed buffer bind reach the generic binding point too
BindBufferBase and BindBufferRange bind the buffer to the indexed point AND to the
generic binding point of the same target (GL 4.6 core 6.1.1); only the indexed half
was implemented. Applications lean on the second half constantly, because it is what
makes the set-up idiom work:

    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo);
    glBufferData(GL_SHADER_STORAGE_BUFFER, size, nullptr, GL_DYNAMIC_DRAW);

With the generic point left at 0 the glBufferData raised GL_INVALID_OPERATION and
the buffer kept its zero size, so the later glMapBufferRange over it failed the
offset+length bound and returned nullptr. The whole KHR-GL40.texture_gather group
verifies its result through exactly that sequence and dereferences the map's return
value without checking it, so 51 of its 75 cases took the process down with a
SIGSEGV inside the test.

Unbinding propagates the same way: buffer 0 clears both points.
2026-08-04 09:40:28 -04:00
BZLZHH 3dc6a1b6db [Fix] (MG_Impl, DirectGLES): answer the texture-gather offset limit queries
glGetIntegerv(GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET) and its GL_MAX_ counterpart fell
through to the default arm of the getter and raised GL_INVALID_ENUM, leaving the
caller's variable untouched - KHR-GL40.texture_gather.api-enums read back the
uninitialised 32764 that happened to be on its stack and failed on the error alone.

Both are core state from GL 4.0 (table 23.53) and from ES 3.1 (table 20.40), so the
value is simply the host driver's, probed alongside the other limits in
FillInGLESCapabilities and carried to the getter through DynamicBackendParameters.
The probe result is widened to the -8/+7 core minimums rather than trusted blindly:
a driver that leaves the out-parameter alone (no ES 3.1, or an enum it ignores) would
otherwise hand us a range narrower than GL 4.0 requires MobileGL to advertise, and
the shaders the CTS builds assume the guaranteed range regardless.
2026-08-04 09:40:15 -04:00
36 changed files with 2080 additions and 156 deletions
+16
View File
@@ -234,8 +234,17 @@ namespace MobileGL {
// drives capture from its draw recording instead (DirectVulkan). End is // drives capture from its draw recording instead (DirectVulkan). End is
// called while the frontend capture state is still active, so the backend // called while the frontend capture state is still active, so the backend
// can still see the capture program and buffer bindings. // 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 (*BeginTransformFeedback)(GLenum primitiveMode);
void (*EndTransformFeedback)(); 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 Int64 (*GetGpuTimestampNs)(); // glGetInteger64v(GL_TIMESTAMP); 0 if unsupported
}; };
struct GlobalBackendFunctionsTable { struct GlobalBackendFunctionsTable {
@@ -288,6 +297,13 @@ namespace MobileGL {
Int MaxIntegerSamples = 1; Int MaxIntegerSamples = 1;
Int MaxSamples = 1; Int MaxSamples = 1;
Int MaxSampleMaskWords = 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 MaxTextureImageUnits = 32;
Int MaxVertexTextureImageUnits = 32; Int MaxVertexTextureImageUnits = 32;
Int MaxComputeTextureImageUnits = 32; Int MaxComputeTextureImageUnits = 32;
@@ -910,7 +910,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
// picks a whole different shader for draw_buffers without // picks a whole different shader for draw_buffers without
// explicit_attrib_location. DirectVulkan advertises both. // explicit_attrib_location. DirectVulkan advertises both.
E_GL_ARB_explicit_attrib_location, E_GL_ARB_texture_multisample, 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 // Only advertised when the device driver actually has usable timer queries
// (GL_EXT_disjoint_timer_query plus its entry points) and the // (GL_EXT_disjoint_timer_query plus its entry points) and the
// MOBILEGL_DISABLE_TIMERQUERY escape hatch is off. // 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 // Transform feedback is captured by the real ES driver rather than
// reconstructed from the draw recording, so the frontend has to hand the // reconstructed from the draw recording, so the frontend has to hand the
// span boundaries over. // span boundaries over.
funcsTable.GL.PatchParameteri = DirectGLES::PatchParameteri;
funcsTable.GL.BeginTransformFeedback = XfbImpl::BeginTransformFeedback; funcsTable.GL.BeginTransformFeedback = XfbImpl::BeginTransformFeedback;
funcsTable.GL.EndTransformFeedback = XfbImpl::EndTransformFeedback; 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; funcsTableInitialized = true;
} }
return funcsTable; return funcsTable;
@@ -1072,6 +1081,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
m_dynamicParameters.MaxIntegerSamples = m_GLESCapabilities.MaxIntegerSamples; m_dynamicParameters.MaxIntegerSamples = m_GLESCapabilities.MaxIntegerSamples;
m_dynamicParameters.MaxSamples = m_GLESCapabilities.MaxSamples; m_dynamicParameters.MaxSamples = m_GLESCapabilities.MaxSamples;
m_dynamicParameters.MaxSampleMaskWords = m_GLESCapabilities.MaxSampleMaskWords; 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 // 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. // 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 // Minecraft's 128-entry Blaze3D GlStateManager.TEXTURES[], iterated by Iris), and the combined
+373 -45
View File
@@ -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) { void SyncBoundBuffer(BufferTarget target, GLenum glTarget) {
#ifdef TRACY_ENABLE #ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND); 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 // 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). // binder (e.g. Flywheel's indirect vertex shaders pull instance data from storage buffers).
SyncBufferBindingPoints(BufferTarget::ShaderStorage, GL_SHADER_STORAGE_BUFFER); SyncBufferBindingPoints(BufferTarget::ShaderStorage, GL_SHADER_STORAGE_BUFFER);
MarkShaderStorageBuffersGpuWritten();
} }
void SyncComputeBuffers(Bool includeDispatchIndirectBuffer) { void SyncComputeBuffers(Bool includeDispatchIndirectBuffer) {
@@ -376,6 +391,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
ProcessDeferredBufferReleases(); ProcessDeferredBufferReleases();
SyncBufferBindingPoints(BufferTarget::Uniform, GL_UNIFORM_BUFFER); SyncBufferBindingPoints(BufferTarget::Uniform, GL_UNIFORM_BUFFER);
SyncBufferBindingPoints(BufferTarget::ShaderStorage, GL_SHADER_STORAGE_BUFFER); SyncBufferBindingPoints(BufferTarget::ShaderStorage, GL_SHADER_STORAGE_BUFFER);
MarkShaderStorageBuffersGpuWritten();
if (includeDispatchIndirectBuffer) { if (includeDispatchIndirectBuffer) {
SyncBoundBuffer(BufferTarget::DispatchIndirect, GL_DISPATCH_INDIRECT_BUFFER); 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 // 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 // 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. // 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 XfbImpl {
namespace { namespace {
struct XfbCaptureTarget { struct XfbCaptureTarget {
@@ -400,10 +421,161 @@ namespace MobileGL::MG_Backend::DirectGLES {
SizeT end = 0; SizeT end = 0;
}; };
Bool g_xfbPending = false; // frontend Begin seen, driver capture not started yet // Per frontend transform feedback object. The default object (name 0) maps to
Bool g_xfbStarted = false; // driver capture running // the driver's default object (id 0) and is always present.
GLenum g_xfbPrimitiveMode = GL_POINTS; struct XfbObjectState {
Vector<XfbCaptureTarget> g_xfbTargets; 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 } // namespace
Bool AreTransformFeedbacksSupported() { Bool AreTransformFeedbacksSupported() {
@@ -414,17 +586,23 @@ namespace MobileGL::MG_Backend::DirectGLES {
void BeginTransformFeedback(GLenum primitiveMode) { void BeginTransformFeedback(GLenum primitiveMode) {
if (!AreTransformFeedbacksSupported()) return; if (!AreTransformFeedbacksSupported()) return;
g_xfbPrimitiveMode = primitiveMode; auto& xfb = CurrentXfb();
g_xfbPending = true; xfb.primitiveMode = primitiveMode;
g_xfbStarted = false; xfb.pending = true;
g_xfbTargets.clear(); xfb.started = false;
xfb.paused = false;
xfb.targets.clear();
} }
// Tail of PrepareForDraw: the program is bound and every buffer the draw needs // 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. // is up to date, so the capture buffers can be bound and the span opened.
void StartPendingTransformFeedback() { void StartPendingTransformFeedback() {
if (!g_xfbPending) return; auto& xfb = CurrentXfb();
g_xfbPending = false; // 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(); const auto& program = MG_State::pGLContext->GetTransformFeedbackProgram();
if (!program) return; if (!program) return;
@@ -443,51 +621,103 @@ namespace MobileGL::MG_Backend::DirectGLES {
const SizeT start = std::min(range.start, bufferObject->GetSize()); const SizeT start = std::min(range.start, bufferObject->GetSize());
const SizeT end = std::min(range.end, bufferObject->GetSize()); const SizeT end = std::min(range.end, bufferObject->GetSize());
if (end <= start) continue; 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); 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() { void EndTransformFeedback() {
g_xfbPending = false; auto& xfb = CurrentXfb();
if (!g_xfbStarted) return; xfb.pending = false;
g_xfbStarted = false; xfb.paused = false;
if (!xfb.started) return;
xfb.started = false;
g_GLESFuncs.glEndTransformFeedback(); g_GLESFuncs.glEndTransformFeedback();
if (xfb.scattered) {
// The GPU wrote the capture buffers behind the frontend's back, so the CPU ScatterCapturedRecords(xfb);
// shadows that back MapBuffer/GetBufferSubData still hold the pre-draw bytes. xfb.scattered = false;
// Mirror the captured ranges into them. Buffers whose storage the backend xfb.scatterProgram.reset();
// already owns (coherent persistent map) need nothing: reads resolve against } else {
// that storage directly. ReadbackCapturedRanges(xfb.targets);
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);
}
}
g_xfbTargets.clear();
} }
// The ES context went away (or is being torn down): the span, its buffer ids and void PauseTransformFeedback() {
// the frontend objects it pinned all belonged to it. 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() { void OnBackendContextDestroyed() {
g_xfbPending = false; g_xfbObjects.clear();
g_xfbStarted = false; g_currentXfbName = 0;
g_xfbTargets.clear(); g_scatterBufferId = 0;
g_scatterBufferSize = 0;
} }
} // namespace XfbImpl } // namespace XfbImpl
@@ -1383,6 +1613,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
const GLenum targetGL = TextureImpl::ConvertTextureTargetToBackendGLEnum(target); 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 // Bind texture object
const auto& backendTextureIt = TextureImpl::g_backendTextureObjects.find(textureObject.get()); const auto& backendTextureIt = TextureImpl::g_backendTextureObjects.find(textureObject.get());
if (backendTextureIt == TextureImpl::g_backendTextureObjects.end()) continue; 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) { void GenerateMipmap(GLenum target) {
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER #if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER
DebugImpl::OpenGLScopeMarker marker(__func__); DebugImpl::OpenGLScopeMarker marker(__func__);
@@ -3718,9 +4039,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
auto& slot = unit.GetBindingSlot(MG_Util::ConvertGLEnumToTextureTarget(target)); auto& slot = unit.GetBindingSlot(MG_Util::ConvertGLEnumToTextureTarget(target));
auto& texture = slot.GetBoundObject(); auto& texture = slot.GetBoundObject();
MOBILEGL_ASSERT(texture != nullptr, "GenerateMipmap requires a bound texture."); 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); 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); auto& backendTexture = TextureImpl::SyncTextureObjectToBackend(texture);
if (IsDepthOnlyFormat(texture->GetFormat())) { 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 // 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 // program current and the capture buffers bound), and the end also mirrors the
// captured bytes back into the frontend buffer shadows. // captured bytes back into the frontend buffer shadows.
void PatchParameteri(GLenum pname, GLint value);
namespace XfbImpl { namespace XfbImpl {
Bool AreTransformFeedbacksSupported(); Bool AreTransformFeedbacksSupported();
void BeginTransformFeedback(GLenum primitiveMode); void BeginTransformFeedback(GLenum primitiveMode);
void EndTransformFeedback(); void EndTransformFeedback();
void PauseTransformFeedback();
void ResumeTransformFeedback();
void BindTransformFeedback(GLuint name);
void DeleteTransformFeedback(GLuint name);
void OnBackendContextDestroyed(); void OnBackendContextDestroyed();
} // namespace XfbImpl } // namespace XfbImpl
+54 -3
View File
@@ -630,6 +630,32 @@ namespace MobileGL::MG_Backend::DirectGLES {
resource->syncedChangeSerial = bufferObject.GetChangeSerial(); 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) { void Ops_OnDestroy(SharedPtr<BackendBufferResource>&& resource) {
if (!resource) return; if (!resource) return;
auto* glesResource = static_cast<GLESBufferResource*>(resource.get()); auto* glesResource = static_cast<GLESBufferResource*>(resource.get());
@@ -662,6 +688,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
.FlushMappedRange = Ops_FlushMappedRange, .FlushMappedRange = Ops_FlushMappedRange,
.OnDestroy = Ops_OnDestroy, .OnDestroy = Ops_OnDestroy,
.AcquirePersistentMap = Ops_AcquirePersistentMap, .AcquirePersistentMap = Ops_AcquirePersistentMap,
.ReadbackFromGpu = Ops_ReadbackFromGpu,
}; };
} // namespace } // namespace
@@ -3357,14 +3384,18 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
// ES has no rectangle sampler, and SPIRV-Cross refuses the whole module rather // ES has no rectangle sampler, and SPIRV-Cross refuses the whole module rather
// than approximating one. Where every use takes integer texel coordinates a // than approximating one. Rewriting the type to 2D is exact for a lookup that
// rectangle image is indistinguishable from a 2D one, so rewrite the type and let // takes integer texel coordinates and needs the coordinate divided by the
// it through; the pass declines anything it cannot convert exactly. // 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; Vector<unsigned int> rectLoweredSpirv;
Bool loweredRectImages = false;
if (MG_Util::ShaderTranspiler::ShaderCompiler::LowerRectImagesForEssl(*effectiveSpirv, if (MG_Util::ShaderTranspiler::ShaderCompiler::LowerRectImagesForEssl(*effectiveSpirv,
rectLoweredSpirv) && rectLoweredSpirv) &&
!rectLoweredSpirv.empty()) { !rectLoweredSpirv.empty()) {
effectiveSpirv = &rectLoweredSpirv; effectiveSpirv = &rectLoweredSpirv;
loweredRectImages = true;
} }
MG_Util::ShaderTranspiler::SpvcSession spvcSession(*effectiveSpirv, MG_Util::ShaderTranspiler::SpvcSession spvcSession(*effectiveSpirv,
@@ -3401,6 +3432,26 @@ namespace MobileGL::MG_Backend::DirectGLES {
source = ForceFlatIntegerVaryings(source, glShaderType); source = ForceFlatIntegerVaryings(source, glShaderType);
source = BroadcastLegacyFragColor(std::move(source), glShaderType, m_fragColorBroadcastCount); source = BroadcastLegacyFragColor(std::move(source), glShaderType, m_fragColorBroadcastCount);
source = EmulateTextureLodBias(source); 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 = EmulateBaseInstanceInVertexShader(std::move(source), glShaderType);
source = PromoteDrawParameterGlobalsToUniforms(std::move(source), glShaderType); source = PromoteDrawParameterGlobalsToUniforms(std::move(source), glShaderType);
source = ForceSupporterOutput(source); source = ForceSupporterOutput(source);
+61
View File
@@ -596,6 +596,67 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
return result; 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 PrgramImpl
namespace Utils { namespace Utils {
+9
View File
@@ -129,6 +129,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
// all have a zero bias is therefore unaffected. Returns the source unchanged when // all have a zero bias is therefore unaffected. Returns the source unchanged when
// there is nothing to rewrite. // there is nothing to rewrite.
String EmulateTextureLodBias(const String& glslCode); 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 PrgramImpl
namespace Utils { namespace Utils {
@@ -1578,6 +1578,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// (and, via the SharedPtrs, the records), never pool slots, so // (and, via the SharedPtrs, the records), never pool slots, so
// stale queries are always safe to delete. // stale queries are always safe to delete.
Uint64 rendererGeneration = 0; 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 } // namespace
@@ -1676,6 +1682,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
primitives)) { primitives)) {
return false; return false;
} }
if (query->kind == VulkanTimerQuery::Kind::XfbGenerated && MG_State::pGLContext != nullptr) {
primitives += MG_State::pGLContext->GetTransformFeedbackPausedPrimitiveCounter() -
query->pausedPrimitiveSnapshot;
}
*outNanoseconds = primitives; *outNanoseconds = primitives;
return true; return true;
} }
@@ -1719,6 +1729,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
auto* query = new VulkanTimerQuery{}; auto* query = new VulkanTimerQuery{};
query->kind = generated ? VulkanTimerQuery::Kind::XfbGenerated : VulkanTimerQuery::Kind::XfbWritten; query->kind = generated ? VulkanTimerQuery::Kind::XfbGenerated : VulkanTimerQuery::Kind::XfbWritten;
query->rendererGeneration = GetRendererGeneration(); query->rendererGeneration = GetRendererGeneration();
query->pausedPrimitiveSnapshot =
MG_State::pGLContext ? MG_State::pGLContext->GetTransformFeedbackPausedPrimitiveCounter() : 0;
return query; return query;
} }
@@ -650,6 +650,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return false; 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{}; BufferSlice slice{};
if (!m_bufferManager->AcquireResidentSlice(BufferKind::ShaderStorage, bufferObject, slice) || !slice.IsValid()) { if (!m_bufferManager->AcquireResidentSlice(BufferKind::ShaderStorage, bufferObject, slice) || !slice.IsValid()) {
MGLOG_E("ResolveStorageBufferDescriptor: failed to sync GL buffer %u for block '%s'", MGLOG_E("ResolveStorageBufferDescriptor: failed to sync GL buffer %u for block '%s'",
@@ -7,6 +7,8 @@
// End of Source File Header // End of Source File Header
#include "VkBufferManager.h" #include "VkBufferManager.h"
#include "../DirectVulkan.h"
#include "VulkanRenderer.h"
namespace MobileGL::MG_Backend::DirectVulkan { namespace MobileGL::MG_Backend::DirectVulkan {
namespace { 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) { void* Ops_AcquirePersistentMap(BufferObject& bufferObject) {
if (g_activeBufferManager) { if (g_activeBufferManager) {
return g_activeBufferManager->AcquirePersistentMap(bufferObject); return g_activeBufferManager->AcquirePersistentMap(bufferObject);
@@ -80,6 +94,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.FlushMappedRange = Ops_FlushMappedRange, .FlushMappedRange = Ops_FlushMappedRange,
.OnDestroy = Ops_OnDestroy, .OnDestroy = Ops_OnDestroy,
.AcquirePersistentMap = Ops_AcquirePersistentMap, .AcquirePersistentMap = Ops_AcquirePersistentMap,
.ReadbackFromGpu = Ops_ReadbackFromGpu,
}; };
} // namespace } // namespace
@@ -2798,6 +2798,10 @@ void main() {
} }
m_vertexInputStateFactory.reset(); m_vertexInputStateFactory.reset();
m_xfbCounterBuffer.Destroy(); m_xfbCounterBuffer.Destroy();
m_xfbCounterSlotByObject.clear();
m_xfbNextCounterSlot = 0;
m_xfbCountersValid.fill(false);
m_xfbLastSeenGeneration.fill(0);
if (m_occlusionQueryPool != VK_NULL_HANDLE) { if (m_occlusionQueryPool != VK_NULL_HANDLE) {
vkDestroyQueryPool(m_device, m_occlusionQueryPool, nullptr); vkDestroyQueryPool(m_device, m_occlusionQueryPool, nullptr);
m_occlusionQueryPool = VK_NULL_HANDLE; 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) { Bool VulkanRenderer::SubmitReadbackCommandsAndWait(FrameContext::FrameData& frame) {
if (frame.isCommandRecording) { if (frame.isCommandRecording) {
m_frameContext.EndCommandRecording(); m_frameContext.EndCommandRecording();
@@ -8000,11 +8015,30 @@ void main() {
resource->layout = finalLayout; 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) { Bool VulkanRenderer::BeginXfbCaptureForDraw(FrameContext::FrameData& frame) {
if (!m_transformFeedbackFeatureEnabled || MG_State::pGLContext == nullptr || if (!m_transformFeedbackFeatureEnabled || MG_State::pGLContext == nullptr ||
!MG_State::pGLContext->IsTransformFeedbackActive()) { !MG_State::pGLContext->IsTransformFeedbackActive()) {
return false; 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(); const auto& program = MG_State::pGLContext->GetTransformFeedbackProgram();
if (!program || program->GetTransformFeedbackVaryingCount() == 0) { if (!program || program->GetTransformFeedbackVaryingCount() == 0) {
return false; return false;
@@ -8017,7 +8051,7 @@ void main() {
if (!m_xfbCounterBuffer.IsValid()) { if (!m_xfbCounterBuffer.IsValid()) {
if (!m_xfbCounterBuffer.Create({ if (!m_xfbCounterBuffer.Create({
.allocator = m_allocator, .allocator = m_allocator,
.size = 16, .size = 16 * kXfbCounterObjectSlots,
.usage = VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT | .usage = VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT |
VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT,
.memoryUsage = VMA_MEMORY_USAGE_AUTO, .memoryUsage = VMA_MEMORY_USAGE_AUTO,
@@ -8058,15 +8092,16 @@ void main() {
s_vkCmdBindTransformFeedbackBuffersEXT(frame.commandBuffer, 0, static_cast<Uint32>(bufferCount), buffers, s_vkCmdBindTransformFeedbackBuffersEXT(frame.commandBuffer, 0, static_cast<Uint32>(bufferCount), buffers,
offsets, sizes); offsets, sizes);
const Uint32 counterSlot = CurrentXfbCounterSlot();
const Uint64 generation = MG_State::pGLContext->GetTransformFeedbackGeneration(); const Uint64 generation = MG_State::pGLContext->GetTransformFeedbackGeneration();
const Bool resume = m_xfbCountersValid && m_xfbLastSeenGeneration == generation; const Bool resume = m_xfbCountersValid[counterSlot] && m_xfbLastSeenGeneration[counterSlot] == generation;
m_xfbLastSeenGeneration = generation; m_xfbLastSeenGeneration[counterSlot] = generation;
VkBuffer counterBuffers[4] = {}; VkBuffer counterBuffers[4] = {};
VkDeviceSize counterOffsets[4] = {}; VkDeviceSize counterOffsets[4] = {};
for (SizeT i = 0; i < bufferCount; ++i) { for (SizeT i = 0; i < bufferCount; ++i) {
counterBuffers[i] = m_xfbCounterBuffer.GetHandle(); 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) { if (resume) {
s_vkCmdBeginTransformFeedbackEXT(frame.commandBuffer, 0, static_cast<Uint32>(bufferCount), s_vkCmdBeginTransformFeedbackEXT(frame.commandBuffer, 0, static_cast<Uint32>(bufferCount),
@@ -8083,15 +8118,16 @@ void main() {
} }
const auto& program = MG_State::pGLContext->GetTransformFeedbackProgram(); const auto& program = MG_State::pGLContext->GetTransformFeedbackProgram();
const SizeT bufferCount = program ? std::min<SizeT>(program->GetTransformFeedbackBufferCount(), 4) : 0; const SizeT bufferCount = program ? std::min<SizeT>(program->GetTransformFeedbackBufferCount(), 4) : 0;
const Uint32 counterSlot = CurrentXfbCounterSlot();
VkBuffer counterBuffers[4] = {}; VkBuffer counterBuffers[4] = {};
VkDeviceSize counterOffsets[4] = {}; VkDeviceSize counterOffsets[4] = {};
for (SizeT i = 0; i < bufferCount; ++i) { for (SizeT i = 0; i < bufferCount; ++i) {
counterBuffers[i] = m_xfbCounterBuffer.GetHandle(); 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, s_vkCmdEndTransformFeedbackEXT(frame.commandBuffer, 0, static_cast<Uint32>(bufferCount), counterBuffers,
counterOffsets); counterOffsets);
m_xfbCountersValid = true; m_xfbCountersValid[counterSlot] = true;
} }
void VulkanRenderer::DrawArrays(const DrawCmd& payload) { 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, void VulkanRenderer::MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect,
GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride) { GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride) {
auto& frame = m_frameContext.GetCurrent(); auto& frame = m_frameContext.GetCurrent();
@@ -8388,11 +8434,11 @@ void main() {
return; return;
} }
if (stride == 0) { 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", MGLOG_E("MultiDrawElementsIndirectCount skipped: stride %d is smaller than command size %zu",
stride, sizeof(DrawIndexedCmdParam)); stride, kGLDrawElementsIndirectCommandBytes);
return; return;
} }
@@ -8411,7 +8457,7 @@ void main() {
const SizeT commandOffset = reinterpret_cast<SizeT>(indirect); const SizeT commandOffset = reinterpret_cast<SizeT>(indirect);
const SizeT commandBytes = commandOffset + 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(); auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
if (!drawBuffer || commandBytes > drawBuffer->GetSize()) { if (!drawBuffer || commandBytes > drawBuffer->GetSize()) {
MGLOG_E("MultiDrawElementsIndirectCount skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range"); MGLOG_E("MultiDrawElementsIndirectCount skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range");
@@ -8489,11 +8535,11 @@ void main() {
return; return;
} }
if (stride == 0) { 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", MGLOG_E("MultiDrawElementsIndirect skipped: stride %d is smaller than command size %zu",
stride, sizeof(DrawIndexedCmdParam)); stride, kGLDrawElementsIndirectCommandBytes);
return; return;
} }
@@ -8512,7 +8558,7 @@ void main() {
const SizeT commandOffset = reinterpret_cast<SizeT>(indirect); const SizeT commandOffset = reinterpret_cast<SizeT>(indirect);
const SizeT commandBytes = commandOffset + 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(); auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
if (!drawBuffer || commandBytes > drawBuffer->GetSize()) { if (!drawBuffer || commandBytes > drawBuffer->GetSize()) {
MGLOG_E("MultiDrawElementsIndirect skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range"); MGLOG_E("MultiDrawElementsIndirect skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range");
@@ -8571,17 +8617,17 @@ void main() {
return; return;
} }
if (stride == 0) { 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", MGLOG_E("MultiDrawArraysIndirect skipped: stride %d is smaller than command size %zu",
stride, sizeof(DrawCmdParam)); stride, kGLDrawArraysIndirectCommandBytes);
return; return;
} }
const SizeT commandOffset = reinterpret_cast<SizeT>(indirect); const SizeT commandOffset = reinterpret_cast<SizeT>(indirect);
const SizeT commandBytes = commandOffset + 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(); auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
if (!drawBuffer || commandBytes > drawBuffer->GetSize()) { if (!drawBuffer || commandBytes > drawBuffer->GetSize()) {
MGLOG_E("MultiDrawArraysIndirect skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range"); 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_vkCmdBeginTransformFeedbackEXT s_vkCmdBeginTransformFeedbackEXT = nullptr;
static inline PFN_vkCmdEndTransformFeedbackEXT s_vkCmdEndTransformFeedbackEXT = nullptr; static inline PFN_vkCmdEndTransformFeedbackEXT s_vkCmdEndTransformFeedbackEXT = nullptr;
// Counter buffers (one 4-byte slot per capture binding) let consecutive // 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; VkBufferObject m_xfbCounterBuffer;
// Non-zero while inside a GL Begin/End with at least one captured draw UnorderedMap<Uint, Uint32> m_xfbCounterSlotByObject;
// recorded; selects counter-buffer resume on the next captured draw. Uint32 m_xfbNextCounterSlot = 0;
Bool m_xfbCountersValid = false; // Set for a slot once a captured draw has been recorded into its span; selects
Uint64 m_xfbLastSeenGeneration = 0; // 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 // Wraps a recorded draw with BeginTransformFeedbackEXT/EndTransformFeedbackEXT
// when GL transform feedback is active; binds capture buffers on demand. // when GL transform feedback is active; binds capture buffers on demand.
Bool BeginXfbCaptureForDraw(FrameContext::FrameData& frame); Bool BeginXfbCaptureForDraw(FrameContext::FrameData& frame);
@@ -788,6 +795,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkImageLayout finalLayout); VkImageLayout finalLayout);
Bool SubmitReadbackCommandsAndWait(FrameContext::FrameData& frame); 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(); void ShutdownSwapchain();
// Static functions // Static functions
+22 -2
View File
@@ -330,12 +330,21 @@ namespace MobileGL::MG_Impl::GLImpl {
} else if (access & BufferMappingAccessBit::Write) { } else if (access & BufferMappingAccessBit::Write) {
*params = GL_WRITE_ONLY; *params = GL_WRITE_ONLY;
} else { } else {
*params = 0; *params = GL_READ_WRITE;
} }
} else { } else {
*params = 0; // Initial value, and what glUnmapBuffer restores (GL 4.6 core table 6.2).
*params = GL_READ_WRITE;
} }
break; 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: case GL_BUFFER_MAPPED:
*params = bufferObject->IsMapped() ? GL_TRUE : GL_FALSE; *params = bufferObject->IsMapped() ? GL_TRUE : GL_FALSE;
break; break;
@@ -878,6 +887,7 @@ namespace MobileGL::MG_Impl::GLImpl {
return; return;
} }
bufferObject->SyncGpuWrites();
bufferObject->DownloadSubData(data, static_cast<SizeT>(offset), static_cast<SizeT>(size)); bufferObject->DownloadSubData(data, static_cast<SizeT>(offset), static_cast<SizeT>(size));
} }
@@ -1366,6 +1376,7 @@ namespace MobileGL::MG_Impl::GLImpl {
if (buffer == 0) { if (buffer == 0) {
point.Bind(nullptr); point.Bind(nullptr);
point.SetRange(Range1D(0, 0)); point.SetRange(Range1D(0, 0));
GetBufferBindingSlot(bufferTarget).Bind(nullptr);
return; return;
} }
@@ -1384,6 +1395,12 @@ namespace MobileGL::MG_Impl::GLImpl {
} else { } else {
point.ClearRange(); 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) { 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) { if (buffer == 0) {
point.Bind(nullptr); point.Bind(nullptr);
point.SetRange(Range1D(0, 0)); point.SetRange(Range1D(0, 0));
GetBufferBindingSlot(bufferTarget).Bind(nullptr);
return; return;
} }
@@ -1424,6 +1442,8 @@ namespace MobileGL::MG_Impl::GLImpl {
} else { } else {
point.ClearRange(); 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@ */ /* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */
+329 -2
View File
@@ -11,6 +11,7 @@
#include <MG_State/GLState/Core.h> #include <MG_State/GLState/Core.h>
#include <MG_State/EGLState/Core.h> #include <MG_State/EGLState/Core.h>
#include <MG_Backend/BackendObjects.h> #include <MG_Backend/BackendObjects.h>
#include "../Getter/GL_Getter.h"
namespace MobileGL::MG_Impl::GLImpl { namespace MobileGL::MG_Impl::GLImpl {
static Bool ValidateCurrentProgramForExecution(const char* functionName) { static Bool ValidateCurrentProgramForExecution(const char* functionName) {
@@ -72,6 +73,12 @@ namespace MobileGL::MG_Impl::GLImpl {
// Geometry amplification is not modelled here. // Geometry amplification is not modelled here.
static void AccountTransformFeedbackPrimitives(GLenum mode, GLsizei count) { static void AccountTransformFeedbackPrimitives(GLenum mode, GLsizei count) {
if (!MG_State::pGLContext->IsTransformFeedbackActive()) return; 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); Uint64 primitives = CountPrimitivesForDraw(mode, count);
if (primitives == 0) return; if (primitives == 0) return;
MG_State::pGLContext->AddTransformFeedbackInputPrimitives(primitives); MG_State::pGLContext->AddTransformFeedbackInputPrimitives(primitives);
@@ -115,7 +122,36 @@ namespace MobileGL::MG_Impl::GLImpl {
MG_State::pGLContext->AddTransformFeedbackCapturedVertices(primitives * verticesPerPrimitive); 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) { 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; const auto& activeBackendObject = MG_Backend::pActiveBackendObject;
if (!activeBackendObject) { if (!activeBackendObject) {
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
@@ -133,11 +169,51 @@ namespace MobileGL::MG_Impl::GLImpl {
return false; 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 // 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 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 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() && if (MG_State::pGLContext->IsTransformFeedbackActive() &&
!MG_State::pGLContext->IsTransformFeedbackPaused() &&
!(MG_State::pGLContext->GetTransformFeedbackProgram() && !(MG_State::pGLContext->GetTransformFeedbackProgram() &&
MG_State::pGLContext->GetTransformFeedbackProgram()->GetShaderIndexByStage(ShaderStage::Geometry) >= 0)) { MG_State::pGLContext->GetTransformFeedbackProgram()->GetShaderIndexByStage(ShaderStage::Geometry) >= 0)) {
const GLenum feedbackMode = MG_State::pGLContext->GetTransformFeedbackPrimitiveMode(); const GLenum feedbackMode = MG_State::pGLContext->GetTransformFeedbackPrimitiveMode();
@@ -168,6 +244,58 @@ namespace MobileGL::MG_Impl::GLImpl {
return true; 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) { void Clear_Backend(GLbitfield mask) {
#ifdef TRACY_ENABLE #ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND); ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
@@ -362,6 +490,28 @@ namespace MobileGL::MG_Impl::GLImpl {
dispatchComputeIndirect(indirect); 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) { void MemoryBarrier(GLbitfield barriers) {
auto memoryBarrier = MG_Backend::gBackendFunctionsTable.GL.MemoryBarrier; auto memoryBarrier = MG_Backend::gBackendFunctionsTable.GL.MemoryBarrier;
if (!memoryBarrier) { if (!memoryBarrier) {
@@ -467,6 +617,8 @@ namespace MobileGL::MG_Impl::GLImpl {
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect) { void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect) {
if (!ValidateCurrentProgramForExecution(__func__)) return; if (!ValidateCurrentProgramForExecution(__func__)) return;
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return; if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
if (!ValidateDrawElementsIndexType(__func__, type)) return;
if (!ValidateIndirectDrawSource(__func__, indirect, kDrawElementsIndirectCommandBytes)) return;
DrawElementsIndirect_Backend(mode, type, indirect); DrawElementsIndirect_Backend(mode, type, indirect);
} }
@@ -486,6 +638,7 @@ namespace MobileGL::MG_Impl::GLImpl {
void DrawArraysIndirect(GLenum mode, const void* indirect) { void DrawArraysIndirect(GLenum mode, const void* indirect) {
if (!ValidateCurrentProgramForExecution(__func__)) return; if (!ValidateCurrentProgramForExecution(__func__)) return;
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return; if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
if (!ValidateIndirectDrawSource(__func__, indirect, kDrawArraysIndirectCommandBytes)) return;
DrawArraysIndirect_Backend(mode, indirect); DrawArraysIndirect_Backend(mode, indirect);
} }
@@ -563,9 +716,12 @@ namespace MobileGL::MG_Impl::GLImpl {
"No program with transform feedback varyings is active.")); "No program with transform feedback varyings is active."));
return; 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(); const SizeT usedBufferCount = program->GetTransformFeedbackBufferCount();
for (SizeT i = 0; i < usedBufferCount; ++i) { 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, const auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback,
static_cast<Uint>(i)); static_cast<Uint>(i));
if (point.GetBoundObject() == nullptr) { if (point.GetBoundObject() == nullptr) {
@@ -679,4 +835,175 @@ namespace MobileGL::MG_Impl::GLImpl {
FixupGsStripCaptureOrder(capturedProgram, inputPrimitives); 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 } // namespace MobileGL::MG_Impl::GLImpl
@@ -13,8 +13,19 @@ namespace MobileGL::MG_Impl::GLImpl {
/* @INSERTION_POINT:FUNCTION_DECLARATION@ */ /* @INSERTION_POINT:FUNCTION_DECLARATION@ */
void BeginTransformFeedback(GLenum primitiveMode); void BeginTransformFeedback(GLenum primitiveMode);
void EndTransformFeedback(void); 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 DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
void DispatchComputeIndirect(GLintptr indirect); void DispatchComputeIndirect(GLintptr indirect);
void PatchParameteri(GLenum pname, GLint value);
void MemoryBarrier(GLbitfield barriers); void MemoryBarrier(GLbitfield barriers);
void MemoryBarrierByRegion(GLbitfield barriers); void MemoryBarrierByRegion(GLbitfield barriers);
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride); 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, 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, 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_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_HEAD(void, BindTransformFeedback, GLenum target, GLuint id) DECLARE_GL_FUNCTION_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_HEAD(void, DeleteTransformFeedbacks, GLsizei n, const GLuint* ids) DECLARE_GL_FUNCTION_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) DECLARE_GL_FUNCTION_HEAD(void, GenTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GenTransformFeedbacks, n, ids)
// Transform feedback objects are not implemented, so no name is ever a live object. The shared DECLARE_GL_FUNCTION_HEAD(GLboolean, IsTransformFeedback, GLuint id) DECLARE_GL_FUNCTION_END(GLboolean, IsTransformFeedback, id)
// stub returns (type)1, telling a probing caller that every id it invents already exists; GL_FALSE DECLARE_GL_FUNCTION_HEAD(void, PauseTransformFeedback) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PauseTransformFeedback)
// is both truthful and what the spec requires for a name that was never generated. DECLARE_GL_FUNCTION_HEAD(void, ResumeTransformFeedback) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ResumeTransformFeedback)
MOBILEGL_GL_API GLboolean glIsTransformFeedback(GLuint id) { 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)
MGLOG_W("Stub function: %s(...)", __FUNCTION__); 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)
return GL_FALSE; 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, 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_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, 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_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) 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, 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, 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, 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, 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, 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) 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, 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, 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, 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_HEAD(void, Uniform1d, GLint location, GLdouble x) DECLARE_GL_FUNCTION_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_HEAD(void, Uniform2d, GLint location, GLdouble x, GLdouble y) DECLARE_GL_FUNCTION_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_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_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_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_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_HEAD(void, Uniform1dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_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_HEAD(void, Uniform2dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_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_HEAD(void, Uniform3dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_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_HEAD(void, Uniform4dv, GLint location, GLsizei count, const GLdouble* value) DECLARE_GL_FUNCTION_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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, 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(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(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) 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, 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, 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, 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_HEAD(void, DrawTransformFeedback, GLenum mode, GLuint id) DECLARE_GL_FUNCTION_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_HEAD(void, DrawTransformFeedbackStream, GLenum mode, GLuint id, GLuint stream) DECLARE_GL_FUNCTION_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_HEAD(void, BeginQueryIndexed, GLenum target, GLuint index, GLuint id) DECLARE_GL_FUNCTION_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_HEAD(void, EndQueryIndexed, GLenum target, GLuint index) DECLARE_GL_FUNCTION_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_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_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_HEAD(void, ProgramUniform1d, GLuint program, GLint location, GLdouble v0) DECLARE_GL_FUNCTION_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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, 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, 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, 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) 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, 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_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, 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_HEAD(void, DrawTransformFeedbackInstanced, GLenum mode, GLuint id, GLsizei instancecount) DECLARE_GL_FUNCTION_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, 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, 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, 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) 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)
+24 -1
View File
@@ -1030,6 +1030,11 @@ namespace MobileGL::MG_Impl::GLImpl {
*params = obj ? static_cast<GLint>(obj->GetExternalIndex()) : 0; *params = obj ? static_cast<GLint>(obj->GetExternalIndex()) : 0;
return; 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: case GL_MAX_DEBUG_GROUP_STACK_DEPTH:
*params = 0; // debug-group entrypoints are stubbed *params = 0; // debug-group entrypoints are stubbed
return; return;
@@ -1912,6 +1917,21 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_MAX_SAMPLE_MASK_WORDS: case GL_MAX_SAMPLE_MASK_WORDS:
*params = dynamicParameters.MaxSampleMaskWords; *params = dynamicParameters.MaxSampleMaskWords;
break; 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: case GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS:
*params = static_cast<GLint>(GetIndexedBufferQueryPointCount(BufferTarget::ShaderStorage)); *params = static_cast<GLint>(GetIndexedBufferQueryPointCount(BufferTarget::ShaderStorage));
break; break;
@@ -1941,7 +1961,10 @@ namespace MobileGL::MG_Impl::GLImpl {
*params = MG_State::pGLContext->IsTransformFeedbackActive() ? 1 : 0; *params = MG_State::pGLContext->IsTransformFeedbackActive() ? 1 : 0;
break; break;
case GL_TRANSFORM_FEEDBACK_PAUSED: 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; break;
case GL_MAX_TEXTURE_IMAGE_UNITS: case GL_MAX_TEXTURE_IMAGE_UNITS:
*params = dynamicParameters.MaxTextureImageUnits; *params = dynamicParameters.MaxTextureImageUnits;
+480 -4
View File
@@ -8,6 +8,8 @@
#include "GL_Program.h" #include "GL_Program.h"
#include "Config.h" #include "Config.h"
#include <cmath>
#include <limits>
#include <MG_Impl/GLImpl/VertexArray/Validators.h> #include <MG_Impl/GLImpl/VertexArray/Validators.h>
#include <MG_State/GLState/Core.h> #include <MG_State/GLState/Core.h>
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h> #include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
@@ -325,11 +327,16 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
void DeleteProgram_State(GLuint program) { 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; if (!CheckProgramNameValidity(program)) return;
MG_State::pGLContext->MarkProgramForDeletion(program); MG_State::pGLContext->MarkProgramForDeletion(program);
} }
void DeleteShader_State(GLuint shader) { 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; if (!CheckShaderNameValidity(shader)) return;
MG_State::pGLContext->MarkShaderForDeletion(shader); MG_State::pGLContext->MarkShaderForDeletion(shader);
} }
@@ -643,6 +650,13 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
case GL_PROGRAM_BINARY_LENGTH: 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_VERTICES_OUT:
case GL_GEOMETRY_INPUT_TYPE: case GL_GEOMETRY_INPUT_TYPE:
@@ -818,7 +832,8 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
if constexpr (std::is_same_v<T, GLfloat>) { 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; auto* pBase = pUBO + offset;
for (int i = 0; i < ttype->getMatrixRows(); i++) { for (int i = 0; i < ttype->getMatrixRows(); i++) {
Memcpy(reinterpret_cast<char*>(params) + ttype->getMatrixCols() * sizeof(GLfloat) * 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); 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) { void GetUniformfv_State(GLuint program, GLint location, GLfloat* params) {
GetUniformScalar_State(program, location, params); GetUniformScalar_State(program, location, params);
} }
@@ -915,9 +967,11 @@ namespace MobileGL::MG_Impl::GLImpl {
void UseProgram_State(GLuint program) { void UseProgram_State(GLuint program) {
MGLOG_D("UseProgram_State: program=%u", program); MGLOG_D("UseProgram_State: program=%u", program);
// GL 3.3 core 2.11.3: the program in use may not change while transform // The program in use may not change while transform feedback is active - unless
// feedback is active (there is no pause in 3.3). // the capture is paused, which is exactly what ARB_transform_feedback2 added the
if (MG_State::pGLContext->IsTransformFeedbackActive()) { // pause for (GL 4.6 core 7.3).
if (MG_State::pGLContext->IsTransformFeedbackActive() &&
!MG_State::pGLContext->IsTransformFeedbackPaused()) {
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation, ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, 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 // Helper function to transpose a 2x2 matrix
void TransposeMatrix2x2(const GLfloat* input, GLfloat* output) { void TransposeMatrix2x2(const GLfloat* input, GLfloat* output) {
// Input matrix is in column-major order (OpenGL default) // Input matrix is in column-major order (OpenGL default)
@@ -1855,6 +1941,312 @@ namespace MobileGL::MG_Impl::GLImpl {
GLuint v[] = {v0, v1, v2, v3}; GLuint v[] = {v0, v1, v2, v3};
Uniform4uiv(location, 1, v); 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) { void Uniform1fv(GLint location, GLsizei count, const GLfloat* value) {
Uniform1fv_State(location, count, value); Uniform1fv_State(location, count, value);
} }
@@ -2251,6 +2643,65 @@ namespace MobileGL::MG_Impl::GLImpl {
ValidateProgram_State(program); 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) { void TransformFeedbackVaryings(GLuint program, GLsizei count, const GLchar* const* varyings, GLenum bufferMode) {
auto& programObject = TryToGetProgramObject(program); auto& programObject = TryToGetProgramObject(program);
if (!programObject) return; if (!programObject) return;
@@ -2279,6 +2730,31 @@ namespace MobileGL::MG_Impl::GLImpl {
for (GLsizei i = 0; i < count; ++i) { for (GLsizei i = 0; i < count; ++i) {
names.emplace_back(varyings != nullptr && varyings[i] != nullptr ? varyings[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); programObject->SetTransformFeedbackVaryings(Move(names), bufferMode);
} }
@@ -137,7 +137,45 @@ namespace MobileGL::MG_Impl::GLImpl {
GLint GetProgramResourceLocation(GLuint program, GLenum programInterface, const GLchar* name); GLint GetProgramResourceLocation(GLuint program, GLenum programInterface, const GLchar* name);
GLint GetProgramResourceLocationIndex(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 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 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 TransformFeedbackVaryings(GLuint program, GLsizei count, const GLchar* const* varyings, GLenum bufferMode);
void GetTransformFeedbackVarying(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLsizei* size, void GetTransformFeedbackVarying(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLsizei* size,
GLenum* type, GLchar* name); GLenum* type, GLchar* name);
@@ -7,6 +7,7 @@
// End of Source File Header // End of Source File Header
#include "GL_Query.h" #include "GL_Query.h"
#include "../Getter/GL_Getter.h"
#include <Config.h> #include <Config.h>
#include <MG_Backend/BackendObjects.h> #include <MG_Backend/BackendObjects.h>
#include <MG_State/GLState/Core.h> #include <MG_State/GLState/Core.h>
@@ -466,4 +467,42 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
*params = static_cast<GLuint64>(value); *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 } // namespace MobileGL::MG_Impl::GLImpl
+3
View File
@@ -16,6 +16,9 @@ namespace MobileGL::MG_Impl::GLImpl {
void BeginQuery(GLenum target, GLuint id); void BeginQuery(GLenum target, GLuint id);
void EndQuery(GLenum target); void EndQuery(GLenum target);
void GetQueryiv(GLenum target, GLenum pname, GLint* params); 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 GetQueryObjectiv(GLuint id, GLenum pname, GLint* params);
void GetQueryObjectuiv(GLuint id, GLenum pname, GLuint* params); void GetQueryObjectuiv(GLuint id, GLenum pname, GLuint* params);
void GetQueryObjecti64v(GLuint id, GLenum pname, GLint64* params); void GetQueryObjecti64v(GLuint id, GLenum pname, GLint64* params);
@@ -174,6 +174,21 @@ namespace MobileGL::MG_State::GLState {
++m_changeSerial; ++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) { void BufferObject::UploadSubData(DataPtr data, SizeT atOffset) {
MOBILEGL_ASSERT(!m_isMapped || (m_mappingAccess & BufferMappingAccessBit::Persistent), MOBILEGL_ASSERT(!m_isMapped || (m_mappingAccess & BufferMappingAccessBit::Persistent),
"Cannot upload sub data while buffer is non-persistently mapped."); "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, "Destination buffer copy out of bounds: dstOffset (%zu) + size (%zu) > m_size (%zu)", dstOffset,
size, m_size); size, m_size);
src->SyncGpuWrites();
Memcpy(m_resource.Bytes() + dstOffset, src->m_resource.Bytes() + srcOffset, size); Memcpy(m_resource.Bytes() + dstOffset, src->m_resource.Bytes() + srcOffset, size);
NotifyContentWrite(dstOffset, size); NotifyContentWrite(dstOffset, size);
} }
void* BufferObject::AcquireMemory(Bool markMapped, Bool read, Bool write) { void* BufferObject::AcquireMemory(Bool markMapped, Bool read, Bool write) {
SyncGpuWrites();
if (markMapped) { if (markMapped) {
m_isMapped = true; m_isMapped = true;
m_mappingAccess = (read ? BufferMappingAccessBit::Read : BufferMappingAccessBit::Null) | 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, MOBILEGL_ASSERT(range.end <= m_size && range.start <= range.end,
"AcquireMemoryRange out of bounds: range (%zu, %zu) exceeds m_size (%zu)", range.start, "AcquireMemoryRange out of bounds: range (%zu, %zu) exceeds m_size (%zu)", range.start,
range.end, m_size); 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_isMapped = true;
m_mappingAccess = access; m_mappingAccess = access;
m_mappedRange = range; m_mappedRange = range;
@@ -99,6 +99,13 @@ namespace MobileGL {
// Must be idempotent: a second call for an already-backed buffer returns the // Must be idempotent: a second call for an already-backed buffer returns the
// same base pointer. // same base pointer.
void* (*AcquirePersistentMap)(BufferObject& bufferObject) = nullptr; 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. // 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. // backend op: the backend storage already holds these bytes.
void WritebackFromBackend(DataPtr data, SizeT atOffset); 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 IsMapped() const;
Bool IsImmutableStorage() const; Bool IsImmutableStorage() const;
SizeT GetSize() const; SizeT GetSize() const;
@@ -196,6 +213,8 @@ namespace MobileGL {
Bool m_isImmutableStorage = false; Bool m_isImmutableStorage = false;
GLbitfield m_storageFlags = 0; GLbitfield m_storageFlags = 0;
Uint64 m_changeSerial = 0; Uint64 m_changeSerial = 0;
// Set by MarkGpuWritten, cleared by SyncGpuWrites once the shadow is refreshed.
Bool m_gpuWritePending = false;
Range1D m_mappedRange; Range1D m_mappedRange;
Vector<Uint8> m_stagingData; Vector<Uint8> m_stagingData;
Bool m_ownsStagingData; Bool m_ownsStagingData;
+97
View File
@@ -424,6 +424,14 @@ namespace MobileGL::MG_State {
m_renderState.SetPointSize(size); 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 { Float GLContext::GetPointSize() const {
return m_renderState.GetPointSize(); return m_renderState.GetPointSize();
} }
@@ -745,6 +753,95 @@ namespace MobileGL::MG_State {
Bool GLContext::ValidateRenderbufferObject(Uint index) const { Bool GLContext::ValidateRenderbufferObject(Uint index) const {
return m_renderbufferState.ValidateRenderbufferObject(index); 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 } // namespace GLState
// Leak-at-exit storage; see GlobalObjects.cpp. // Leak-at-exit storage; see GlobalObjects.cpp.
+82 -2
View File
@@ -138,6 +138,8 @@ namespace MobileGL {
Float GetLineWidth() const; Float GetLineWidth() const;
void SetPointSize(Float size); void SetPointSize(Float size);
Float GetPointSize() const; Float GetPointSize() const;
void SetPatchVertices(Uint vertices);
Uint GetPatchVertices() const;
void SetPolygonOffset(Float factor, Float units); void SetPolygonOffset(Float factor, Float units);
Float GetPolygonOffsetFactor() const; Float GetPolygonOffsetFactor() const;
Float GetPolygonOffsetUnits() const; Float GetPolygonOffsetUnits() const;
@@ -211,20 +213,30 @@ namespace MobileGL {
void SetScissorBox(IntVec4 box); // x, y, width, height void SetScissorBox(IntVec4 box); // x, y, width, height
const IntVec4& GetScissorBox() const; // 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) { void BeginTransformFeedback(GLenum primitiveMode, const SharedPtr<ProgramObject>& program) {
m_transformFeedbackActive = true; m_transformFeedbackActive = true;
m_transformFeedbackPaused = false;
m_transformFeedbackPrimitiveMode = primitiveMode; m_transformFeedbackPrimitiveMode = primitiveMode;
m_transformFeedbackProgram = program; m_transformFeedbackProgram = program;
++m_transformFeedbackGeneration; m_transformFeedbackGeneration = ++m_transformFeedbackNextGeneration;
m_transformFeedbackCapturedVertices = 0; m_transformFeedbackCapturedVertices = 0;
m_transformFeedbackInputPrimitives = 0; m_transformFeedbackInputPrimitives = 0;
} }
void EndTransformFeedback() { void EndTransformFeedback() {
m_transformFeedbackActive = false; m_transformFeedbackActive = false;
m_transformFeedbackPaused = false;
m_transformFeedbackProgram.reset(); 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 IsTransformFeedbackActive() const { return m_transformFeedbackActive; }
Bool IsTransformFeedbackPaused() const { return m_transformFeedbackPaused; }
void SetTransformFeedbackPaused(Bool paused) { m_transformFeedbackPaused = paused; }
GLenum GetTransformFeedbackPrimitiveMode() const { return m_transformFeedbackPrimitiveMode; } GLenum GetTransformFeedbackPrimitiveMode() const { return m_transformFeedbackPrimitiveMode; }
const SharedPtr<ProgramObject>& GetTransformFeedbackProgram() const { const SharedPtr<ProgramObject>& GetTransformFeedbackProgram() const {
return m_transformFeedbackProgram; return m_transformFeedbackProgram;
@@ -239,6 +251,15 @@ namespace MobileGL {
m_transformFeedbackPrimitiveCounter += primitives; m_transformFeedbackPrimitiveCounter += primitives;
} }
Uint64 GetTransformFeedbackPrimitiveCounter() const { return m_transformFeedbackPrimitiveCounter; } 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 // Vertices already captured since BeginTransformFeedback (drives the
// buffer-capacity clamp on the primitives-written accounting). // buffer-capacity clamp on the primitives-written accounting).
void AddTransformFeedbackCapturedVertices(Uint64 vertices) { void AddTransformFeedbackCapturedVertices(Uint64 vertices) {
@@ -252,6 +273,32 @@ namespace MobileGL {
} }
Uint64 GetTransformFeedbackInputPrimitives() const { return m_transformFeedbackInputPrimitives; } 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 // Framebuffer
void GenFramebufferNames(Uint number, Vector<Uint>& framebuffers); void GenFramebufferNames(Uint number, Vector<Uint>& framebuffers);
const SharedPtr<FramebufferObject>& GetFramebufferObject(Uint index); const SharedPtr<FramebufferObject>& GetFramebufferObject(Uint index);
@@ -285,12 +332,45 @@ namespace MobileGL {
VertexArrayState m_vertexArrayState; VertexArrayState m_vertexArrayState;
Array<CurrentVertexAttributeValue, VertexArrayObject::MAX_VERTEX_ATTRIBS> m_currentVertexAttributes{}; Array<CurrentVertexAttributeValue, VertexArrayObject::MAX_VERTEX_ATTRIBS> m_currentVertexAttributes{};
Bool m_transformFeedbackActive = false; Bool m_transformFeedbackActive = false;
Bool m_transformFeedbackPaused = false;
GLenum m_transformFeedbackPrimitiveMode = GL_POINTS; GLenum m_transformFeedbackPrimitiveMode = GL_POINTS;
SharedPtr<ProgramObject> m_transformFeedbackProgram; SharedPtr<ProgramObject> m_transformFeedbackProgram;
Uint64 m_transformFeedbackGeneration = 0; 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_transformFeedbackPrimitiveCounter = 0;
Uint64 m_transformFeedbackPausedPrimitiveCounter = 0;
Uint64 m_transformFeedbackCapturedVertices = 0; Uint64 m_transformFeedbackCapturedVertices = 0;
Uint64 m_transformFeedbackInputPrimitives = 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; TextureState m_textureState;
ProgramState m_programState; ProgramState m_programState;
RenderState m_renderState; RenderState m_renderState;
@@ -174,6 +174,9 @@ namespace MobileGL::MG_State::GLState {
m_xfbStrides.clear(); m_xfbStrides.clear();
m_xfbBufferMode = GL_INTERLEAVED_ATTRIBS; m_xfbBufferMode = GL_INTERLEAVED_ATTRIBS;
m_xfbVaryingNameMaxLength = 0; m_xfbVaryingNameMaxLength = 0;
m_xfbNeedsScatteredCapture = false;
m_xfbPackedStride = 0;
m_gsInputPrimitive = GL_NONE;
m_linkStatus = false; m_linkStatus = false;
} }
@@ -222,6 +225,8 @@ namespace MobileGL::MG_State::GLState {
m_xfbStrides.clear(); m_xfbStrides.clear();
m_xfbBufferMode = m_requestedXfbBufferMode; m_xfbBufferMode = m_requestedXfbBufferMode;
m_xfbVaryingNameMaxLength = 0; m_xfbVaryingNameMaxLength = 0;
m_xfbNeedsScatteredCapture = false;
m_xfbPackedStride = 0;
if (m_requestedXfbVaryings.empty()) { if (m_requestedXfbVaryings.empty()) {
return true; return true;
} }
@@ -243,8 +248,27 @@ namespace MobileGL::MG_State::GLState {
const Bool interleaved = m_xfbBufferMode == GL_INTERLEAVED_ATTRIBS; const Bool interleaved = m_xfbBufferMode == GL_INTERLEAVED_ATTRIBS;
Uint32 interleavedOffset = 0; 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) { for (SizeT i = 0; i < m_requestedXfbVaryings.size(); ++i) {
const String& name = m_requestedXfbVaryings[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) { for (SizeT j = 0; j < i; ++j) {
if (m_requestedXfbVaryings[j] == name) { if (m_requestedXfbVaryings[j] == name) {
m_infoLog = "Transform feedback varying '" + name + "' is specified more than once."; 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.byteSize = bytesPerElement * static_cast<Uint32>(varying.size);
varying.packedOffsetBytes = m_xfbPackedStride;
m_xfbPackedStride += varying.byteSize;
if (interleaved) { if (interleaved) {
varying.bufferIndex = 0; varying.bufferIndex = interleavedBufferIndex;
varying.offsetBytes = interleavedOffset; varying.offsetBytes = interleavedOffset;
interleavedOffset += varying.byteSize; interleavedOffset += varying.byteSize;
} else { } else {
varying.bufferIndex = static_cast<Uint32>(i); varying.bufferIndex = static_cast<Uint32>(m_xfbVaryings.size());
varying.offsetBytes = 0; varying.offsetBytes = 0;
} }
m_xfbVaryingNameMaxLength = m_xfbVaryingNameMaxLength =
@@ -301,13 +327,22 @@ namespace MobileGL::MG_State::GLState {
constexpr Uint32 kMaxSeparateAttribs = 4; constexpr Uint32 kMaxSeparateAttribs = 4;
constexpr Uint32 kMaxSeparateComponents = 4; constexpr Uint32 kMaxSeparateComponents = 4;
constexpr Uint32 kMaxInterleavedComponents = 64; constexpr Uint32 kMaxInterleavedComponents = 64;
constexpr Uint32 kMaxTransformFeedbackBuffers = 4;
if (interleaved) { if (interleaved) {
if (interleavedOffset > kMaxInterleavedComponents * 4) { interleavedStrides.push_back(interleavedOffset);
if (interleavedStrides.size() > kMaxTransformFeedbackBuffers) {
m_infoLog = "Transform feedback capture uses more buffers than "
"GL_MAX_TRANSFORM_FEEDBACK_BUFFERS.";
return false;
}
for (const Uint32 stride : interleavedStrides) {
if (stride > kMaxInterleavedComponents * 4) {
m_infoLog = "Transform feedback interleaved capture exceeds " m_infoLog = "Transform feedback interleaved capture exceeds "
"GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS."; "GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS.";
return false; return false;
} }
m_xfbStrides.assign(1, interleavedOffset); }
m_xfbStrides = Move(interleavedStrides);
} else { } else {
if (m_xfbVaryings.size() > kMaxSeparateAttribs) { if (m_xfbVaryings.size() > kMaxSeparateAttribs) {
m_infoLog = "Transform feedback separate capture exceeds " m_infoLog = "Transform feedback separate capture exceeds "
@@ -542,6 +577,20 @@ namespace MobileGL::MG_State::GLState {
return; 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); MGLOG_D("ProgramObject %u: Starting reflection", m_externalIndex);
DoReflection(); DoReflection();
MGLOG_D("ProgramObject %u: Reflection done (linkStatus=%d)", m_externalIndex, (int)m_linkStatus); 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 GetDeleteStatus() const { return m_deleteStatus; }
Bool GetLinkStatus() const { return m_linkStatus; } 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; } Bool GetValidateStatus() const { return m_validateStatus; }
Int GetActiveAtomicCounterCount() const { return m_program->getNumAtomicCounters(); } Int GetActiveAtomicCounterCount() const { return m_program->getNumAtomicCounters(); }
Int GetActiveAttributesCount() const { return m_program->getNumPipeInputs(); } Int GetActiveAttributesCount() const { return m_program->getNumPipeInputs(); }
@@ -474,6 +485,9 @@ namespace MobileGL::MG_State::GLState {
Uint32 bufferIndex = 0; // capture buffer slot Uint32 bufferIndex = 0; // capture buffer slot
Uint32 offsetBytes = 0; // offset within the capture buffer Uint32 offsetBytes = 0; // offset within the capture buffer
Uint32 byteSize = 0; // bytes captured per vertex for this varying 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) { void SetTransformFeedbackVaryings(Vector<String>&& names, GLenum bufferMode) {
m_requestedXfbVaryings = Move(names); m_requestedXfbVaryings = Move(names);
@@ -491,12 +505,25 @@ namespace MobileGL::MG_State::GLState {
} }
SizeT GetTransformFeedbackBufferCount() const { return m_xfbStrides.size(); } SizeT GetTransformFeedbackBufferCount() const { return m_xfbStrides.size(); }
Int GetTransformFeedbackVaryingMaxLength() const { return m_xfbVaryingNameMaxLength; } 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 // 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 // statically-known emit sequence: the Vulkan capture order then needs the GL
// odd-triangle vertex swap after EndTransformFeedback. // odd-triangle vertex swap after EndTransformFeedback.
Bool HasGsTriangleStripCaptureFixup() const { return m_gsStripCaptureFixup; } Bool HasGsTriangleStripCaptureFixup() const { return m_gsStripCaptureFixup; }
// Triangles per strip, in emission order, for ONE geometry invocation. // Triangles per strip, in emission order, for ONE geometry invocation.
const Vector<Uint32>& GetGsStripTriangles() const { return m_gsStripTriangles; } 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; } Uint GetExternalIndex() const { return m_externalIndex; }
// Globally-unique, never-reused id for this program object's lifetime. Unlike the GL // 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; String m_infoLog;
Bool m_deleteStatus = false; Bool m_deleteStatus = false;
Bool m_linkStatus = false; Bool m_linkStatus = false;
Bool m_binaryRetrievableHint = false;
Bool m_validateStatus = true; Bool m_validateStatus = true;
Uint32 m_backendStateVersion = 0; Uint32 m_backendStateVersion = 0;
@@ -604,7 +632,10 @@ namespace MobileGL::MG_State::GLState {
Vector<Uint32> m_xfbStrides; Vector<Uint32> m_xfbStrides;
Vector<Uint32> m_gsStripTriangles; Vector<Uint32> m_gsStripTriangles;
Bool m_gsStripCaptureFixup = false; Bool m_gsStripCaptureFixup = false;
GLenum m_gsInputPrimitive = GL_NONE;
GLenum m_xfbBufferMode = GL_INTERLEAVED_ATTRIBS; GLenum m_xfbBufferMode = GL_INTERLEAVED_ATTRIBS;
Int m_xfbVaryingNameMaxLength = 0; Int m_xfbVaryingNameMaxLength = 0;
Bool m_xfbNeedsScatteredCapture = false;
Uint32 m_xfbPackedStride = 0;
}; };
} // namespace MobileGL::MG_State::GLState } // namespace MobileGL::MG_State::GLState
@@ -154,6 +154,17 @@ namespace MobileGL {
return m_parameters.PointSize; 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) { void RenderState::SetPolygonOffset(Float factor, Float units) {
if (m_parameters.PolygonOffsetFactor == factor && m_parameters.PolygonOffsetUnits == units) return; 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 IntVec4 Viewport = IntVec4(0, 0, 0, 0); // x, y, width, height
Float LineWidth = 1.0f; Float LineWidth = 1.0f;
Float PointSize = 1.0f; Float PointSize = 1.0f;
// GL_PATCH_VERTICES: how many vertices one tessellation patch consumes.
Uint PatchVertices = 3;
Float PolygonOffsetFactor = 0.0f; Float PolygonOffsetFactor = 0.0f;
Float PolygonOffsetUnits = 0.0f; Float PolygonOffsetUnits = 0.0f;
@@ -319,6 +321,8 @@ namespace MobileGL {
Float GetLineWidth() const; Float GetLineWidth() const;
void SetPointSize(Float size); void SetPointSize(Float size);
Float GetPointSize() const; Float GetPointSize() const;
void SetPatchVertices(Uint vertices);
Uint GetPatchVertices() const;
void SetPolygonOffset(Float factor, Float units); void SetPolygonOffset(Float factor, Float units);
Float GetPolygonOffsetFactor() const; Float GetPolygonOffsetFactor() const;
Float GetPolygonOffsetUnits() const; Float GetPolygonOffsetUnits() const;
@@ -24,6 +24,19 @@ namespace MobileGL {
TextureObjectBase::TextureObjectBase(TextureTarget target, Uint externalIndex) TextureObjectBase::TextureObjectBase(TextureTarget target, Uint externalIndex)
: m_externalIndex(externalIndex), m_lifetimeId(AllocateLifetimeId()), m_target(target) { : m_externalIndex(externalIndex), m_lifetimeId(AllocateLifetimeId()), m_target(target) {
m_sampler = MakeShared<SamplerObject>(0); 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 { TextureInternalFormat TextureObjectBase::GetFormat() const {
@@ -335,6 +348,58 @@ namespace MobileGL {
// TODO: add other texture types as needed // 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 GLState
} // namespace MG_State } // namespace MG_State
} // namespace MobileGL } // namespace MobileGL
@@ -156,6 +156,14 @@ namespace MobileGL::MG_State::GLState {
? static_cast<TextureObjectMipmap*>(texture) ? static_cast<TextureObjectMipmap*>(texture)
: nullptr; : 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) { inline const TextureObjectMipmap* AsMipmapTexture(const ITextureObject* texture) {
return (texture && texture->GetStorageType() == TextureStorageType::Mipmap) return (texture && texture->GetStorageType() == TextureStorageType::Mipmap)
? static_cast<const TextureObjectMipmap*>(texture) ? static_cast<const TextureObjectMipmap*>(texture)
@@ -916,6 +916,12 @@ namespace MobileGL::MG_Util::BackendLoader {
GLfloat minFragmentInterpolationOffset = -0.5f; GLfloat minFragmentInterpolationOffset = -0.5f;
GLfloat maxFragmentInterpolationOffset = 0.4375f; GLfloat maxFragmentInterpolationOffset = 0.4375f;
GLint fragmentInterpolationOffsetBits = 4; 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_ALIASED_LINE_WIDTH_RANGE, aliasedLineWidthRange);
glesFuncs.glGetFloatv(GL_SMOOTH_LINE_WIDTH_RANGE, smoothLineWidthRange); glesFuncs.glGetFloatv(GL_SMOOTH_LINE_WIDTH_RANGE, smoothLineWidthRange);
glesFuncs.glGetFloatv(GL_SMOOTH_LINE_WIDTH_GRANULARITY, &smoothLineWidthGranularity); 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 // 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. // to what is actually implemented instead of forwarding the raw driver limit.
maxSampleMaskWords = std::min(maxSampleMaskWords, 1); 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_TEXTURE_IMAGE_UNITS, &maxTextureImageUnits);
glesFuncs.glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxVertexTextureImageUnits); glesFuncs.glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxVertexTextureImageUnits);
glesFuncs.glGetIntegerv(GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS, &maxComputeTextureImageUnits); glesFuncs.glGetIntegerv(GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS, &maxComputeTextureImageUnits);
@@ -1027,6 +1041,10 @@ namespace MobileGL::MG_Util::BackendLoader {
caps.MaxIntegerSamples = maxIntegerSamples; caps.MaxIntegerSamples = maxIntegerSamples;
caps.MaxSamples = maxSamples; caps.MaxSamples = maxSamples;
caps.MaxSampleMaskWords = maxSampleMaskWords; caps.MaxSampleMaskWords = maxSampleMaskWords;
caps.MaxPatchVertices = maxPatchVertices;
caps.MaxTessGenLevel = maxTessGenLevel;
caps.MinProgramTextureGatherOffset = minProgramTextureGatherOffset;
caps.MaxProgramTextureGatherOffset = maxProgramTextureGatherOffset;
caps.MaxTextureImageUnits = maxTextureImageUnits; caps.MaxTextureImageUnits = maxTextureImageUnits;
caps.MaxVertexTextureImageUnits = maxVertexTextureImageUnits; caps.MaxVertexTextureImageUnits = maxVertexTextureImageUnits;
caps.MaxComputeTextureImageUnits = maxComputeTextureImageUnits; caps.MaxComputeTextureImageUnits = maxComputeTextureImageUnits;
@@ -1102,6 +1102,10 @@ namespace MobileGL {
Int MaxIntegerSamples = 1; Int MaxIntegerSamples = 1;
Int MaxSamples = 1; Int MaxSamples = 1;
Int MaxSampleMaskWords = 1; Int MaxSampleMaskWords = 1;
Int MaxPatchVertices = 32;
Int MaxTessGenLevel = 64;
Int MinProgramTextureGatherOffset = -8;
Int MaxProgramTextureGatherOffset = 7;
Int MaxTextureImageUnits = 32; Int MaxTextureImageUnits = 32;
Int MaxVertexTextureImageUnits = 32; Int MaxVertexTextureImageUnits = 32;
Int MaxComputeTextureImageUnits = 32; Int MaxComputeTextureImageUnits = 32;
@@ -73,7 +73,7 @@ namespace MobileGL {
} }
} }
GLbitfield ConvertBufferMappingAccessToGLEnum(BufferMappingAccessBit access) { GLbitfield ConvertBufferMappingAccessToGLEnum(Flags<BufferMappingAccessBit> access) {
GLbitfield result = 0; GLbitfield result = 0;
if (access & BufferMappingAccessBit::Read) result |= GL_MAP_READ_BIT; if (access & BufferMappingAccessBit::Read) result |= GL_MAP_READ_BIT;
if (access & BufferMappingAccessBit::Write) result |= GL_MAP_WRITE_BIT; if (access & BufferMappingAccessBit::Write) result |= GL_MAP_WRITE_BIT;
@@ -14,6 +14,6 @@ namespace MobileGL {
namespace MG_Util { namespace MG_Util {
GLenum ConvertBufferTargetToGLEnum(BufferTarget bufferTarget); GLenum ConvertBufferTargetToGLEnum(BufferTarget bufferTarget);
GLenum ConvertBufferUsageToGLEnum(BufferUsage usage); GLenum ConvertBufferUsageToGLEnum(BufferUsage usage);
GLbitfield ConvertBufferMappingAccessToGLEnum(BufferMappingAccessBit access); GLbitfield ConvertBufferMappingAccessToGLEnum(Flags<BufferMappingAccessBit> access);
} // namespace MG_Util } // namespace MG_Util
} // namespace MobileGL } // namespace MobileGL
@@ -397,11 +397,19 @@ namespace MobileGL {
} }
} else { } else {
switch (opcode) { switch (opcode) {
// Everything that takes normalized coordinates. Tracing each one back to // Normalized-coordinate lookups whose ESSL form the backend's
// its image type would let a module mix a normalized 2D lookup with a // NormalizeRectSamplerCoordinates post-pass cannot repair: the
// rectangle fetch, but the extra reach is not worth the risk of getting // coordinate is either fused with something else in a single argument
// the trace wrong: decline the whole module instead. // (the Dref sample forms carry the compare value in coord.z) or the
case spv::Op::OpImageSampleImplicitLod: // 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::OpImageSampleExplicitLod:
case spv::Op::OpImageSampleDrefImplicitLod: case spv::Op::OpImageSampleDrefImplicitLod:
case spv::Op::OpImageSampleDrefExplicitLod: case spv::Op::OpImageSampleDrefExplicitLod:
@@ -409,8 +417,6 @@ namespace MobileGL {
case spv::Op::OpImageSampleProjExplicitLod: case spv::Op::OpImageSampleProjExplicitLod:
case spv::Op::OpImageSampleProjDrefImplicitLod: case spv::Op::OpImageSampleProjDrefImplicitLod:
case spv::Op::OpImageSampleProjDrefExplicitLod: case spv::Op::OpImageSampleProjDrefExplicitLod:
case spv::Op::OpImageGather:
case spv::Op::OpImageDrefGather:
case spv::Op::OpImageSparseSampleImplicitLod: case spv::Op::OpImageSparseSampleImplicitLod:
case spv::Op::OpImageSparseSampleExplicitLod: case spv::Op::OpImageSparseSampleExplicitLod:
case spv::Op::OpImageSparseSampleDrefImplicitLod: case spv::Op::OpImageSparseSampleDrefImplicitLod: