mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
583 lines
36 KiB
C++
583 lines
36 KiB
C++
// MobileGL - MobileGL/MG_Backend/BackendObject.h
|
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
|
// Licensed under the GNU Lesser General Public License v3.0:
|
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// End of Source File Header
|
|
|
|
#pragma once
|
|
#include <Includes.h>
|
|
#include "MG_State/GLState/TextureState/TextureEnum.h"
|
|
|
|
namespace MobileGL {
|
|
namespace MG_State::GLState {
|
|
class FramebufferObject;
|
|
class ITextureObject;
|
|
class RenderbufferObject;
|
|
}
|
|
|
|
enum class BackendType {
|
|
DirectGLES,
|
|
DirectVulkan,
|
|
BackendTypeCount,
|
|
Unknown = -1
|
|
};
|
|
|
|
namespace MG_Backend {
|
|
// One endpoint of a glCopyImageSubData. GL 4.6 core 18.3.2 accepts GL_RENDERBUFFER
|
|
// alongside the ten whole-image texture targets, and a renderbuffer name lives in a
|
|
// namespace of its own - so an endpoint is a sum type, not an ITextureObject. At most
|
|
// one of the two pointers is set; neither is set when the name named nothing, which is
|
|
// the INVALID_VALUE the frontend validator reports.
|
|
struct CopyImageEndpoint {
|
|
SharedPtr<MG_State::GLState::ITextureObject> Texture;
|
|
SharedPtr<MG_State::GLState::RenderbufferObject> Renderbuffer;
|
|
|
|
Bool IsRenderbuffer() const { return Renderbuffer != nullptr; }
|
|
Bool Exists() const { return Texture != nullptr || Renderbuffer != nullptr; }
|
|
};
|
|
|
|
enum class FormatCapability : Uint64 {
|
|
Creatable = 1ull << 0,
|
|
|
|
Sampled = 1ull << 1,
|
|
LinearFilter = 1ull << 2,
|
|
GenerateMipmap = 1ull << 3,
|
|
TextureGather = 1ull << 4,
|
|
TextureShadow = 1ull << 5,
|
|
|
|
FramebufferRenderable = 1ull << 6,
|
|
FramebufferLayered = 1ull << 7,
|
|
MultisampleTexture = 1ull << 8,
|
|
MultisampleRenderbuffer = 1ull << 9,
|
|
|
|
ColorAttachment = 1ull << 10,
|
|
DepthAttachment = 1ull << 11,
|
|
StencilAttachment = 1ull << 12,
|
|
|
|
TextureBuffer = 1ull << 13
|
|
};
|
|
|
|
using FormatCapabilityFlags = Flags<FormatCapability>;
|
|
|
|
inline constexpr Array<FormatCapability, 14> kReportedFormatCapabilities = {
|
|
FormatCapability::Creatable,
|
|
FormatCapability::Sampled,
|
|
FormatCapability::LinearFilter,
|
|
FormatCapability::GenerateMipmap,
|
|
FormatCapability::TextureGather,
|
|
FormatCapability::TextureShadow,
|
|
FormatCapability::FramebufferRenderable,
|
|
FormatCapability::FramebufferLayered,
|
|
FormatCapability::MultisampleTexture,
|
|
FormatCapability::MultisampleRenderbuffer,
|
|
FormatCapability::ColorAttachment,
|
|
FormatCapability::DepthAttachment,
|
|
FormatCapability::StencilAttachment,
|
|
FormatCapability::TextureBuffer,
|
|
};
|
|
|
|
inline constexpr SizeT kFormatCapabilityTextureTargetCount =
|
|
static_cast<SizeT>(TextureTarget::TextureTargetCount);
|
|
inline constexpr SizeT kFormatCapabilityRenderbufferTargetIndex = kFormatCapabilityTextureTargetCount;
|
|
inline constexpr SizeT kFormatCapabilityTargetCount = kFormatCapabilityTextureTargetCount + 1;
|
|
inline constexpr SizeT kFormatCapabilityFormatCount =
|
|
static_cast<SizeT>(TextureInternalFormat::TextureInternalFormatCount);
|
|
|
|
using FormatCapabilityTable =
|
|
Array<Array<FormatCapabilityFlags, kFormatCapabilityFormatCount>, kFormatCapabilityTargetCount>;
|
|
using FormatSampleCountTable =
|
|
Array<Array<Vector<Int>, kFormatCapabilityFormatCount>, kFormatCapabilityTargetCount>;
|
|
|
|
struct FormatCapabilityCache {
|
|
FormatCapabilityTable FullCaps{};
|
|
FormatCapabilityTable CaveatCaps{};
|
|
FormatSampleCountTable SampleCounts{};
|
|
|
|
void Clear();
|
|
};
|
|
|
|
Bool HasFormatCapability(FormatCapabilityFlags caps, FormatCapability capability);
|
|
SizeT GetFormatCapabilityTargetIndex(TextureTarget target);
|
|
SizeT GetRenderbufferFormatCapabilityTargetIndex();
|
|
const char* GetFormatCapabilityName(FormatCapability capability);
|
|
String GetFormatCapabilityTargetName(SizeT targetIndex);
|
|
void PrintFormatCapabilities(const FormatCapabilityCache& cache);
|
|
|
|
// Opaque backend fence-sync handle, created by GLFunctionsTable::FenceSync
|
|
// and released by GLFunctionsTable::DeleteSync.
|
|
using BackendSyncHandle = void*;
|
|
|
|
// Opaque backend timer-query handle, created by
|
|
// GLFunctionsTable::BeginTimeElapsedQuery / QueryCounterTimestamp and
|
|
// released by GLFunctionsTable::DeleteBackendQuery.
|
|
using BackendQueryHandle = void*;
|
|
|
|
struct GLFunctionsTable {
|
|
void (*DrawArrays)(GLenum mode, GLint first, GLsizei count);
|
|
void (*DrawElements)(GLenum mode, GLsizei count, GLenum type, const void* indices);
|
|
void (*DrawElementsBaseVertex)(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
|
GLint basevertex);
|
|
void (*MultiDrawArrays)(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount);
|
|
void (*MultiDrawElements)(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
|
|
GLsizei drawcount);
|
|
void (*MultiDrawElementsBaseVertex)(GLenum mode, const GLsizei* count, GLenum type,
|
|
const GLvoid* const* indices, GLsizei drawcount,
|
|
const GLint* basevertex);
|
|
void (*MultiDrawElementsIndirect)(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount,
|
|
GLsizei stride);
|
|
void (*MultiDrawArraysIndirect)(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);
|
|
void (*MultiDrawElementsIndirectCount)(GLenum mode, GLenum type, const void* indirect,
|
|
GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride);
|
|
void (*MultiDrawArraysIndirectCount)(GLenum mode, const void* indirect, GLintptr drawcount,
|
|
GLsizei maxdrawcount, GLsizei stride);
|
|
void (*DrawRangeElementsBaseVertex)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
|
const void* indices, GLint basevertex);
|
|
void (*DrawRangeElements)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
|
const void* indices);
|
|
void (*DrawElementsInstancedBaseVertexBaseInstance)(GLenum mode, GLsizei count, GLenum type,
|
|
const void* indices, GLsizei instancecount,
|
|
GLint basevertex, GLuint baseinstance);
|
|
void (*DrawElementsInstancedBaseVertex)(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
|
GLsizei instancecount, GLint basevertex);
|
|
void (*DrawElementsInstancedBaseInstance)(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
|
GLsizei instancecount, GLuint baseinstance);
|
|
void (*DrawElementsInstanced)(GLenum mode, GLsizei count, GLenum type, const void* indices,
|
|
GLsizei instancecount);
|
|
void (*DrawArraysInstancedBaseInstance)(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
|
|
GLuint baseinstance);
|
|
void (*DrawArraysInstanced)(GLenum mode, GLint first, GLsizei count, GLsizei instancecount);
|
|
void (*DrawElementsIndirect)(GLenum mode, GLenum type, const void* indirect);
|
|
void (*DrawArraysIndirect)(GLenum mode, const void* indirect);
|
|
void (*Clear)(GLbitfield mask);
|
|
void (*ClearBufferfi)(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
|
void (*ClearBufferfv)(GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
|
void (*ClearBufferuiv)(GLenum buffer, GLint drawbuffer, const GLuint* value);
|
|
void (*ClearBufferiv)(GLenum buffer, GLint drawbuffer, const GLint* value);
|
|
void (*ClearNamedFramebufferfv)(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
|
GLenum buffer, GLint drawbuffer, const GLfloat* value);
|
|
void (*ClearNamedFramebufferfi)(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
|
GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
|
|
void (*ClearNamedFramebufferiv)(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
|
GLenum buffer, GLint drawbuffer, const GLint* value);
|
|
void (*ClearNamedFramebufferuiv)(const SharedPtr<MG_State::GLState::FramebufferObject>& framebuffer,
|
|
GLenum buffer, GLint drawbuffer, const GLuint* value);
|
|
void (*BlitFramebuffer)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0,
|
|
GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
|
|
void (*BlitNamedFramebuffer)(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
|
|
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFramebuffer,
|
|
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
|
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
|
|
GLbitfield mask, GLenum filter);
|
|
void (*CopyTexImage2D)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
|
|
GLsizei height, GLint border);
|
|
void (*CopyTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
|
|
GLsizei width, GLsizei height);
|
|
void (*CopyImageSubData)(const CopyImageEndpoint& src,
|
|
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
|
|
const CopyImageEndpoint& dst,
|
|
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
|
|
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth);
|
|
void (*GenerateMipmap)(GLenum target);
|
|
void (*ReadPixels)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
|
|
void* pixels);
|
|
void (*GetTexImage)(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels);
|
|
void (*GetTextureImage)(const SharedPtr<MG_State::GLState::ITextureObject>& texture,
|
|
TextureUploadTarget uploadTarget, GLint level, GLenum format, GLenum type,
|
|
GLsizei bufSize, GLvoid* pixels);
|
|
void (*DispatchCompute)(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
|
|
void (*DispatchComputeIndirect)(GLintptr indirect);
|
|
void (*MemoryBarrier)(GLbitfield barriers);
|
|
void (*MemoryBarrierByRegion)(GLbitfield barriers);
|
|
void (*BindImageTexture)(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer,
|
|
GLenum access, GLenum format);
|
|
void (*GetIntegeri_v)(GLenum target, GLuint index, GLint* data);
|
|
void (*GetInteger64i_v)(GLenum target, GLuint index, GLint64* data);
|
|
void (*GetProgramiv)(GLuint program, GLenum pname, GLint* params);
|
|
// The GL program interface (glGetProgramInterfaceiv / glGetProgramResource*) is NOT
|
|
// a backend query: it describes the program the application wrote, in the
|
|
// application's namespace, which neither backend program is in. It is answered
|
|
// entirely by MG_Impl/GLImpl/Program/ProgramInterface from the frontend reflection.
|
|
// Takes the block's GL NAME, not glShaderStorageBlockBinding's index. The index
|
|
// the application passes is the frontend interface-query enumeration's, and no
|
|
// backend shares that index space: DirectVulkan enumerates SPIR-V descriptor
|
|
// bindings and DirectGLES asks a real driver about SPIRV-Cross-generated ESSL.
|
|
// The name is the one coordinate all three agree on, so the frontend resolves the
|
|
// index against its own enumeration and each backend maps the name to its own.
|
|
void (*ShaderStorageBlockBinding)(GLuint program, const GLchar* storageBlockName,
|
|
GLuint storageBlockBinding);
|
|
// GL fence sync objects. All entries are optional (may be null); the
|
|
// frontend then falls back to always-signaled sync semantics.
|
|
// FenceSync may itself return null when the backend cannot create a
|
|
// fence right now (e.g. the calling thread does not own the backend
|
|
// context); the frontend treats such a sync as always signaled.
|
|
BackendSyncHandle (*FenceSync)();
|
|
GLenum (*ClientWaitSync)(BackendSyncHandle sync, GLbitfield flags, GLuint64 timeout);
|
|
void (*WaitSync)(BackendSyncHandle sync, GLbitfield flags, GLuint64 timeout);
|
|
void (*DeleteSync)(BackendSyncHandle sync);
|
|
Bool (*GetSyncStatus)(BackendSyncHandle sync); // true = signaled
|
|
// GL timer-query objects (GL_ARB_timer_query). All entries are
|
|
// optional (may be null); the frontend then falls back to zero
|
|
// results and reports GL_QUERY_COUNTER_BITS == 0.
|
|
// BeginTimeElapsedQuery / QueryCounterTimestamp may themselves
|
|
// return null when the backend cannot create a query right now;
|
|
// the frontend treats such a query as immediately available with
|
|
// a zero result.
|
|
// Dynamic support check: true only when the live backend can
|
|
// actually time at the moment of the call (extension / entry
|
|
// points / timestamp valid bits are known then, not at table
|
|
// init). Gates the advertised GL_QUERY_COUNTER_BITS.
|
|
Bool (*IsTimerQuerySupported)();
|
|
BackendQueryHandle (*BeginTimeElapsedQuery)(); // starts a TIME_ELAPSED span
|
|
void (*EndTimeElapsedQuery)(BackendQueryHandle query); // ends the span
|
|
BackendQueryHandle (*QueryCounterTimestamp)(); // glQueryCounter(GL_TIMESTAMP) one-shot
|
|
Bool (*IsQueryResultAvailable)(BackendQueryHandle query); // non-blocking
|
|
// Returns true when a final value was produced (*outNanoseconds
|
|
// written; the frontend may cache it and release the handle).
|
|
// Returns false when the result could not be obtained YET - e.g.
|
|
// a Vulkan wait that refuses to block on a not-yet-submitted
|
|
// frame serial - in which case the frontend must keep the handle
|
|
// and leave the query readable later.
|
|
Bool (*GetQueryResult64)(BackendQueryHandle query, Bool wait, Uint64* outNanoseconds);
|
|
void (*DeleteBackendQuery)(BackendQueryHandle query);
|
|
// GL_SAMPLES_PASSED occlusion queries (optional; null = unsupported,
|
|
// the frontend then rejects the target). Results/deletion flow through
|
|
// GetQueryResult64 / DeleteBackendQuery like timer queries.
|
|
BackendQueryHandle (*BeginOcclusionQuery)();
|
|
void (*EndOcclusionQuery)(BackendQueryHandle query);
|
|
// Transform feedback primitive queries backed by real GPU query pools
|
|
// (optional; null = frontend falls back to CPU accounting).
|
|
BackendQueryHandle (*BeginXfbPrimitivesQuery)(Bool generated);
|
|
void (*EndXfbPrimitivesQuery)(BackendQueryHandle query);
|
|
// Whether GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN should be answered from the
|
|
// frontend's own accounting wherever that accounting is exact - a capture with no
|
|
// geometry stage - instead of from the query above. Set by DirectGLES, whose result
|
|
// is whatever the ES driver's PRIMITIVES_WRITTEN counter says: Adreno reports twice
|
|
// the written count for a vertex-only capture that follows a large render pass,
|
|
// where the desktop-exact answer is the one the frontend already computed. Defaults
|
|
// to false, so a backend that never sets it keeps using its GPU result.
|
|
Bool PrefersCpuXfbPrimitiveAccounting = false;
|
|
// Transform feedback capture spans, for backends whose own GL/ES driver
|
|
// performs the capture (DirectGLES). Both optional; null means the backend
|
|
// drives capture from its draw recording instead (DirectVulkan). End is
|
|
// called while the frontend capture state is still active, so the backend
|
|
// can still see the capture program and buffer bindings.
|
|
// GL_PATCH_VERTICES; ES 3.2 spells it the same way.
|
|
void (*PatchParameteri)(GLenum pname, GLint value);
|
|
void (*BeginTransformFeedback)(GLenum primitiveMode);
|
|
void (*EndTransformFeedback)();
|
|
// ARB_transform_feedback2. A backend that leaves these null keeps the single
|
|
// implicit capture span the frontend has always modelled; the frontend state
|
|
// (paused flag, per-object bindings) is tracked either way.
|
|
void (*PauseTransformFeedback)();
|
|
void (*ResumeTransformFeedback)();
|
|
void (*BindTransformFeedback)(GLuint name);
|
|
void (*DeleteTransformFeedback)(GLuint name);
|
|
Int64 (*GetGpuTimestampNs)(); // glGetInteger64v(GL_TIMESTAMP); 0 if unsupported
|
|
};
|
|
struct GlobalBackendFunctionsTable {
|
|
GLFunctionsTable GL;
|
|
void (*Present)();
|
|
// Optional: applies the app-requested eglSwapInterval to the native
|
|
// presentation path (null = backend keeps its own pacing policy).
|
|
void (*SetSwapInterval)(Int interval);
|
|
};
|
|
|
|
// Coarse GPU vendor identity for gating device-specific quirks. Detected from the
|
|
// Vulkan physical-device vendorID or the GLES GL_VENDOR/GL_RENDERER strings; stays
|
|
// Unknown when detection is inconclusive, in which case auto-gated quirks stay off.
|
|
enum class GpuVendorKind : Uint8 {
|
|
Unknown = 0,
|
|
Qualcomm,
|
|
Arm,
|
|
Nvidia,
|
|
Amd,
|
|
Intel,
|
|
ImgTec,
|
|
// Software rasterizers (llvmpipe/lavapipe, SwiftShader).
|
|
Software,
|
|
};
|
|
|
|
struct DynamicBackendParameters {
|
|
SizeT UniformBufferOffsetAlignment = 256;
|
|
// GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT. 1.0 means the backend cannot filter anisotropically,
|
|
// which is also why the extension is not advertised in that case.
|
|
Float MaxTextureMaxAnisotropy = 1.0f;
|
|
Float AliasedLineWidthRangeMin = 1.0f;
|
|
Float AliasedLineWidthRangeMax = 1.0f;
|
|
Float SmoothLineWidthRangeMin = 1.0f;
|
|
Float SmoothLineWidthRangeMax = 1.0f;
|
|
Float SmoothLineWidthGranularity = 1.0f;
|
|
Float PointSizeRangeMin = 1.0f;
|
|
Float PointSizeRangeMax = 1.0f;
|
|
Float PointSizeGranularity = 1.0f;
|
|
Int Max3DTextureSize = 16384;
|
|
Int MaxArrayTextureLayers = 2048;
|
|
Int MaxCubeMapTextureSize = 16384;
|
|
Int MaxFramebufferWidth = 16384;
|
|
Int MaxFramebufferHeight = 16384;
|
|
Int MaxFramebufferLayers = 2048;
|
|
Int MaxRenderbufferSize = 16384;
|
|
Int MaxTextureSize = 16384;
|
|
Int MaxColorTextureSamples = 1;
|
|
Int MaxDepthTextureSamples = 1;
|
|
Int MaxFramebufferSamples = 1;
|
|
Int MaxIntegerSamples = 1;
|
|
Int MaxSamples = 1;
|
|
Int MaxSampleMaskWords = 1;
|
|
// Tessellation limits; defaults are the GL 4.0 core minimums.
|
|
Int MaxPatchVertices = 32;
|
|
Int MaxTessGenLevel = 64;
|
|
// GL_MIN/MAX_PROGRAM_TEXTURE_GATHER_OFFSET. Defaults are the GL 4.0 core
|
|
// minimums, which every ES 3.1 driver also guarantees.
|
|
Int MinProgramTextureGatherOffset = -8;
|
|
Int MaxProgramTextureGatherOffset = 7;
|
|
Int MaxTextureImageUnits = 32;
|
|
Int MaxVertexTextureImageUnits = 32;
|
|
Int MaxComputeTextureImageUnits = 32;
|
|
Int MaxCombinedTextureImageUnits = 192;
|
|
Int MaxVertexAttribs = 16;
|
|
Int MaxComputeShaderStorageBlocks = 8;
|
|
Int MaxCombinedShaderStorageBlocks = 32;
|
|
// Per-stage GL_MAX_*_SHADER_STORAGE_BLOCKS. Zero is a legal answer for the four
|
|
// non-compute, non-fragment stages and these defaults are the spec minimums, not
|
|
// placeholders: GL 4.6 table 23.64 and ES 3.2 table 21.44 both set the minimum for
|
|
// vertex, tessellation control, tessellation evaluation and geometry at 0, and only
|
|
// fragment (8 in GL, 4 in ES) and compute are guaranteed to have any. Every real ARM
|
|
// GLES driver takes that allowance - a Mali-G925 reports 0 for all four - so a
|
|
// backend that cannot honour a graphics-stage storage block MUST report 0 here
|
|
// rather than a hopeful number. Advertising a non-zero count the driver will refuse
|
|
// does not make the block work; it only moves the failure from an honest
|
|
// "unsupported" at query time to a backend link error the frontend never surfaces,
|
|
// after which every draw with that program silently renders nothing.
|
|
Int MaxVertexShaderStorageBlocks = 0;
|
|
Int MaxTessControlShaderStorageBlocks = 0;
|
|
Int MaxTessEvaluationShaderStorageBlocks = 0;
|
|
Int MaxGeometryShaderStorageBlocks = 0;
|
|
Int MaxFragmentShaderStorageBlocks = 8;
|
|
Int MaxComputeUniformBlocks = 12;
|
|
Int MaxComputeWorkGroupInvocations = 128;
|
|
Int MaxShaderStorageBufferBindings = 8;
|
|
Int MaxTextureBufferSize = 65536;
|
|
// GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT; 1 means the offset is unconstrained.
|
|
Int TextureBufferOffsetAlignment = 1;
|
|
Int MaxUniformBufferBindings = 24;
|
|
Int MaxUniformBlockSize = 16384;
|
|
Int MaxImageUnits = 8;
|
|
Int MaxCombinedImageUniforms = 8;
|
|
Int MaxVertexImageUniforms = 0;
|
|
Int MaxGeometryImageUniforms = 0;
|
|
Int MaxFragmentImageUniforms = 8;
|
|
Int MaxComputeImageUniforms = 8;
|
|
Int MaxDrawBuffers = 8;
|
|
Int MaxColorAttachments = 8;
|
|
// GL_MAX_CLIP_DISTANCES. Zero is a legal answer here, not a placeholder, and a
|
|
// backend that cannot host a clip distance MUST report it: advertising eight the
|
|
// backend will refuse does not make gl_ClipDistance work, it only moves the failure
|
|
// from an honest "unsupported" at query time to a backend shader-compile error the
|
|
// frontend never surfaces, after which every draw with that program silently renders
|
|
// nothing. DirectGLES fills it from GL_EXT_clip_cull_distance, DirectVulkan from the
|
|
// shaderClipDistance device feature. The DEFAULT stays at the GL 4.3 core minimum
|
|
// because it describes the no-backend case (standalone shader compiles, unit tests),
|
|
// where there is no device to be honest about and BuildTBuiltInResource still has to
|
|
// hand glslang a workable gl_MaxClipDistances.
|
|
Int MaxClipDistances = 8;
|
|
Int MaxViewports = 16;
|
|
// GL_LAYER_PROVOKING_VERTEX / GL_VIEWPORT_INDEX_PROVOKING_VERTEX: which vertex of a
|
|
// primitive supplies gl_Layer and gl_ViewportIndex. GL 4.6 table 23.65 makes
|
|
// GL_UNDEFINED_VERTEX a legal answer for both, and it is the honest default - naming
|
|
// a convention is a statement about behaviour, so a backend that does not pin one
|
|
// must not claim it does. DirectGLES fills the layer one from the ES 3.2 query and
|
|
// the viewport one from GL_OES_viewport_array, and leaves UNDEFINED where the
|
|
// capability is absent: without the viewport array extension only viewport 0 is ever
|
|
// rasterized, so no convention selects anything. DirectVulkan keeps UNDEFINED for
|
|
// both - which vertex provokes is decided per pipeline by
|
|
// VulkanRenderer::SelectProvokingVertexMode out of VK_EXT_provoking_vertex,
|
|
// provokingVertexModePerPipeline and the topology, so no single convention is true
|
|
// of the backend.
|
|
GLenum LayerProvokingVertex = GL_UNDEFINED_VERTEX;
|
|
GLenum ViewportIndexProvokingVertex = GL_UNDEFINED_VERTEX;
|
|
Int MaxViewportWidth = 16384;
|
|
Int MaxViewportHeight = 16384;
|
|
Float ViewportBoundsRangeMin = 0.0f;
|
|
Float ViewportBoundsRangeMax = 0.0f;
|
|
Int ViewportSubpixelBits = 0;
|
|
// GL 4.x fragment-interpolation offset limits. These defaults are the
|
|
// core minimums and are replaced by live GLES/Vulkan device limits.
|
|
Float MinFragmentInterpolationOffset = -0.5f;
|
|
// For four fractional bits the greatest required legal offset is
|
|
// 0.5 - 2^-4 = 0.4375 (GL 4.6 table 23.70).
|
|
Float MaxFragmentInterpolationOffset = 0.4375f;
|
|
Int FragmentInterpolationOffsetBits = 4;
|
|
Bool SupportsWideLines = false;
|
|
// Whether a framebuffer whose depth and stencil attachments are distinct
|
|
// images can be rendered to. GL only requires support when both refer to the
|
|
// same image and lets an implementation answer GL_FRAMEBUFFER_UNSUPPORTED
|
|
// otherwise, which is what DirectVulkan (one combined attachment) and the
|
|
// real ES drivers behind DirectGLES both do. Defaults to true so a backend
|
|
// that never sets it keeps the permissive behaviour.
|
|
Bool SupportsDistinctDepthStencilAttachments = true;
|
|
// Whether attaching a single layer of a 3D or array texture to a framebuffer actually
|
|
// renders to that layer. DirectGLES hands the layer straight to
|
|
// glFramebufferTextureLayer, so it does; DirectVulkan maps a GL layer onto a Vulkan
|
|
// array layer with no notion of a 3D depth slice, so it does not yet. Defaults to false
|
|
// so a backend that never sets it gets the conservative answer.
|
|
// Which layered texture targets this backend can attach ONE layer of to a framebuffer
|
|
// and then really clear, render and read back that layer. Bit (1u << TextureTarget) is
|
|
// set for each supported target. Deliberately per target rather than one flag: the three
|
|
// ways a GL layer maps onto Vulkan are independent capabilities. A 2D or 2D multisample
|
|
// array layer IS a VkImage array layer and needs nothing extra; a 3D texture's layer is
|
|
// a z slice, which needs a 2D-array-compatible image and a per-slice clear that
|
|
// vkCmdClearColorImage cannot express; a cube map array needs an image shape and the
|
|
// imageCubeArray feature before it can be attached at any layer at all. Defaults to 0 so
|
|
// a backend that never sets it gets the conservative answer.
|
|
Uint32 PerLayerFramebufferAttachmentTargets = 0;
|
|
|
|
static constexpr Uint32 PerLayerFramebufferAttachmentBit(TextureTarget target) {
|
|
return (static_cast<Int>(target) >= 0 &&
|
|
static_cast<Int>(target) < static_cast<Int>(TextureTarget::TextureTargetCount))
|
|
? (1u << static_cast<Uint32>(target))
|
|
: 0u;
|
|
}
|
|
|
|
Bool SupportsPerLayerFramebufferAttachment(TextureTarget target) const {
|
|
const Uint32 bit = PerLayerFramebufferAttachmentBit(target);
|
|
return bit != 0 && (PerLayerFramebufferAttachmentTargets & bit) != 0;
|
|
}
|
|
// Whether this backend can CONSUME a shader module that still declares 64-bit floats,
|
|
// i.e. whether `double` survives the transpile instead of being narrowed to `float`
|
|
// (ShaderTranspiler::DemoteFloat64Pass). Detected, never assumed:
|
|
// * DirectVulkan sets it from VkPhysicalDeviceFeatures::shaderFloat64, the feature
|
|
// VUID-VkShaderModuleCreateInfo-pCode-08740 requires before a module declaring
|
|
// OpCapability Float64 may be created at all. lavapipe has it; Adreno and Mali
|
|
// both report VK_FALSE, so no real mobile device does.
|
|
// * DirectGLES can NEVER have it. GLSL ES has no 64-bit float type in any version
|
|
// or extension, so SPIRV-Cross cannot emit one ("FP64 not supported in ES
|
|
// profile") and the demotion there is mathematically mandatory, always.
|
|
// Defaults to false so a backend that never sets it - and the no-backend case, which
|
|
// is what standalone shader compiles and the unit tests run under - keeps the
|
|
// demotion, which is the behaviour that works everywhere.
|
|
Bool SupportsShaderFloat64 = false;
|
|
// Whether glVertexAttribLFormat / glVertexArrayAttribLFormat can be honoured, i.e.
|
|
// whether a 64-bit vertex attribute can actually reach a shader unconverted. Detected,
|
|
// never assumed: DirectVulkan needs VkPhysicalDeviceFeatures::shaderFloat64 (the
|
|
// attribute travels as its 32-bit word pair, so no VK_FORMAT_R64* is required, but the
|
|
// bitcast result is Float64); DirectGLES can never have it, ESSL having no fp64 type at
|
|
// all. Defaults to false so a backend that never sets it gets the conservative answer.
|
|
//
|
|
// INDEPENDENT of SupportsShaderFloat64, and it has to be: this flag decides a VkFormat
|
|
// from the VAO ATTRIBUTE alone, which does not know what type the shader declared, and
|
|
// glVertexAttribFormat(GL_DOUBLE) feeding a plain `in vec4` is both legal and common
|
|
// (KHR-GL43.vertex_attrib_binding.basic-input-case4/5, advanced-bindingUpdate). A
|
|
// backend with native fp64 that still cannot FETCH 64 bits keeps this false and relies
|
|
// on the per-MODULE rule in ShaderCompiler::SanitizeAndOptimizeBinary instead: a vertex
|
|
// module that declares a 64-bit float INPUT is demoted whole, so the two shader-side
|
|
// halves (PackDoubleVertexInputsPass and VertexInputStateFactory::ToVkVertexFormat)
|
|
// still see one consistent world.
|
|
Bool SupportsFloat64VertexAttributes = false;
|
|
SizeT MaxShaderStorageBlockSize = 128 * 1024 * 1024;
|
|
Uint32 SubgroupSize = 0;
|
|
Uint32 SubgroupSupportedStages = 0;
|
|
Uint32 SubgroupSupportedFeatures = 0;
|
|
Bool SubgroupQuadOperationsInAllStages = false;
|
|
GpuVendorKind GpuVendor = GpuVendorKind::Unknown;
|
|
};
|
|
|
|
enum class WindowBackend {
|
|
Android,
|
|
X11,
|
|
MetalLayer,
|
|
Win32, // Handle is an HWND
|
|
// TODO: Wayland, etc.
|
|
WindowBackendCount,
|
|
Unknown = -1
|
|
};
|
|
|
|
struct WindowHandle {
|
|
WindowBackend Backend = WindowBackend::Unknown;
|
|
void* Handle = nullptr;
|
|
Uint32 Width = 0;
|
|
Uint32 Height = 0;
|
|
};
|
|
|
|
class BackendObject {
|
|
public:
|
|
virtual ~BackendObject() = default;
|
|
|
|
virtual void Initialize() = 0;
|
|
virtual Bool InitCapabilities() = 0;
|
|
virtual Bool InitWindowSurface() = 0;
|
|
|
|
virtual Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor);
|
|
virtual Bool CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle);
|
|
virtual Bool ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height);
|
|
virtual Bool CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height);
|
|
virtual Bool MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);
|
|
virtual Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw);
|
|
// Forwards the app-requested eglSwapInterval to the backend's native
|
|
// presentation path (no-op for backends without a SetSwapInterval hook).
|
|
virtual void SetEGLSwapInterval(Int interval);
|
|
virtual void ReleaseEGLSurface(EGLSurface surface);
|
|
virtual void ReleaseEGLResources();
|
|
|
|
void SetWindowHandle(const WindowHandle& handle);
|
|
|
|
virtual const RendererInfo& GetRendererInfo() const = 0;
|
|
virtual String GetBackendAPIVersionString() const = 0;
|
|
virtual const GlobalBackendFunctionsTable& GetBackendFunctions() const = 0;
|
|
virtual const DynamicBackendParameters& GetDynamicParameters() const = 0;
|
|
const FormatCapabilityCache& GetFormatCapabilities() const;
|
|
virtual BackendType GetBackendType() const = 0;
|
|
|
|
protected:
|
|
enum class SurfaceKind {
|
|
None,
|
|
Window,
|
|
Pbuffer
|
|
};
|
|
|
|
struct EGLCurrentState {
|
|
EGLDisplay Display = EGL_NO_DISPLAY;
|
|
EGLSurface DrawSurface = EGL_NO_SURFACE;
|
|
EGLSurface ReadSurface = EGL_NO_SURFACE;
|
|
EGLContext Context = EGL_NO_CONTEXT;
|
|
};
|
|
|
|
struct EGLSurfaceState {
|
|
SurfaceKind Kind = SurfaceKind::None;
|
|
Bool DestroyPending = false;
|
|
WindowHandle Window;
|
|
EGLint Width = 1;
|
|
EGLint Height = 1;
|
|
};
|
|
|
|
void ResetEGLRuntimeState();
|
|
Bool RegisterEGLWindowSurface(EGLSurface surface, const WindowHandle& handle);
|
|
Bool RegisterEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height);
|
|
const EGLSurfaceState* GetRegisteredEGLSurface(EGLSurface surface) const;
|
|
Bool ActivateEGLSurface(EGLSurface surface);
|
|
virtual Bool InitPbufferSurface(EGLint width, EGLint height);
|
|
virtual void OnEGLSurfaceReleased(EGLSurface surface);
|
|
FormatCapabilityCache& MutableFormatCapabilities();
|
|
|
|
mutable std::recursive_mutex m_eglStateMutex;
|
|
FormatCapabilityCache m_formatCapabilities;
|
|
WindowHandle m_windowHandle;
|
|
EGLDisplay m_eglDisplay = EGL_NO_DISPLAY;
|
|
EGLSurface m_eglSurface = EGL_NO_SURFACE;
|
|
Bool m_eglDisplayInitialized = false;
|
|
Bool m_eglSurfaceInitialized = false;
|
|
Bool m_backendCapabilitiesInitialized = false;
|
|
SurfaceKind m_eglSurfaceKind = SurfaceKind::None;
|
|
UnorderedMap<std::thread::id, EGLCurrentState> m_eglCurrentThreads;
|
|
UnorderedMap<EGLSurface, EGLSurfaceState> m_eglSurfaces;
|
|
|
|
private:
|
|
Bool IsEGLSurfaceCurrent(EGLSurface surface) const;
|
|
void DestroyPendingEGLSurfaceIfUnused(EGLSurface surface);
|
|
void ReleaseEGLCurrentThread(const std::thread::id& threadKey);
|
|
};
|
|
} // namespace MG_Backend
|
|
} // namespace MobileGL
|