mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
Compare commits
2
Commits
ad7340921e
...
1aa5cf63a6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1aa5cf63a6 | ||
|
|
55fa9cdfdc |
@@ -496,6 +496,8 @@ set(SOURCE_FILES
|
||||
|
||||
MobileGL/MG_Backend/Init.cpp
|
||||
MobileGL/MG_Backend/BackendObject.cpp
|
||||
MobileGL/MG_Backend/BackendFunctions.cpp
|
||||
MobileGL/MG_Backend/FormatCapabilities.cpp
|
||||
MobileGL/MG_Backend/ClientReadback.cpp
|
||||
MobileGL/MG_Backend/GpuHangWatch.cpp
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// MobileGL - MobileGL/MG_Backend/BackendFunctions.cpp
|
||||
// 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
|
||||
|
||||
#include "BackendFunctions.h"
|
||||
|
||||
namespace MobileGL::MG_Backend {
|
||||
namespace {
|
||||
MG_Util::Damage::Region& PresentDamageSlot() {
|
||||
thread_local MG_Util::Damage::Region damage = MG_Util::Damage::Region::Full();
|
||||
return damage;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
const MG_Util::Damage::Region& CurrentPresentDamage() {
|
||||
return PresentDamageSlot();
|
||||
}
|
||||
void SetCurrentPresentDamage(const MG_Util::Damage::Region& damage) {
|
||||
PresentDamageSlot() = damage;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend
|
||||
@@ -0,0 +1,244 @@
|
||||
// MobileGL - MobileGL/MG_Backend/BackendFunctions.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"
|
||||
#include <MG_Util/Damage/Damage.h>
|
||||
#include <MG_Pipe/MGPipeHandles.h>
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
class FramebufferObject;
|
||||
class ITextureObject;
|
||||
class RenderbufferObject;
|
||||
} // namespace MobileGL::MG_State::GLState
|
||||
|
||||
namespace MobileGL::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;
|
||||
MG_Pipe::MGPipeHandle TextureHandle = MG_Pipe::kMGPipeNullHandle;
|
||||
MG_Pipe::MGPipeHandle RenderbufferHandle = MG_Pipe::kMGPipeNullHandle;
|
||||
|
||||
Bool IsRenderbuffer() const {
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(RenderbufferHandle)) return true;
|
||||
return Renderbuffer != nullptr;
|
||||
}
|
||||
Bool Exists() const {
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(TextureHandle)) return true;
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(RenderbufferHandle)) return true;
|
||||
return Texture != nullptr || Renderbuffer != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
// 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);
|
||||
// The by-name form: the destination is `texture`, whatever the units hold. `target` is
|
||||
// the one the record carries (the texture's own 2D-class target).
|
||||
void (*CopyTextureSubImage2D)(const SharedPtr<MG_State::GLState::ITextureObject>& texture, 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);
|
||||
// The by-name form, also used once the bound-target form has resolved its texture.
|
||||
void (*GenerateTextureMipmap)(const SharedPtr<MG_State::GLState::ITextureObject>& texture, 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);
|
||||
// The ONLY indexed query that is genuinely a backend one, and only for the pnames
|
||||
// MG_Impl/GLImpl/Getter/GL_Getter.cpp does not already own. Every indexed pname that
|
||||
// names FRONTEND state - the indexed buffer bindings, the per-unit texture/sampler
|
||||
// bindings, the image-unit bindings, the viewport rectangles, the indexed capabilities
|
||||
// - is answered in GL_Getter::GetIntegeri_v and never reaches this entry; the
|
||||
// 64-bit and float/double widths are derived there from the same answer, which is why
|
||||
// no GetInteger64i_v/GetFloati_v/GetDoublei_v table entry exists. In practice this
|
||||
// leaves GL_MAX_COMPUTE_WORK_GROUP_COUNT / _SIZE (also asked directly by
|
||||
// MG_Util/ShaderTranspiler/CompileEnv.cpp) plus whatever pname the frontend has no
|
||||
// case for at all.
|
||||
void (*GetIntegeri_v)(GLenum target, GLuint index, GLint* data);
|
||||
// There is deliberately NO GetProgramiv entry: glGetProgramiv describes the program
|
||||
// the APPLICATION wrote - link status, the transform-feedback mode, the compute local
|
||||
// size - all of which are frontend link artifacts on ProgramObject, and
|
||||
// MG_Impl/GLImpl/Program/GL_Program.cpp answers every one of them from there. Asking a
|
||||
// backend would mean asking about a DIFFERENT program (a SPIRV-Cross-generated ESSL
|
||||
// one, or a SPIR-V module), in a namespace the application never sees.
|
||||
// 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);
|
||||
};
|
||||
|
||||
// THE DAMAGE OF THE PRESENT BEING APPLIED on this thread: the server's apply thread sets it
|
||||
// around the backend's Present (from the present record), and a backend that can hand damage
|
||||
// to its native present reads it there. Full whenever nothing set it.
|
||||
const MG_Util::Damage::Region& CurrentPresentDamage();
|
||||
void SetCurrentPresentDamage(const MG_Util::Damage::Region& damage);
|
||||
|
||||
extern GlobalBackendFunctionsTable gBackendFunctionsTable;
|
||||
} // namespace MobileGL::MG_Backend
|
||||
@@ -7,11 +7,7 @@
|
||||
// End of Source File Header
|
||||
|
||||
#include "BackendObject.h"
|
||||
#include "MG_Util/Converters/MGToStr/TextureEnumConverter.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
namespace MobileGL::MG_Backend {
|
||||
namespace {
|
||||
@@ -23,146 +19,8 @@ namespace MobileGL::MG_Backend {
|
||||
std::thread::id CurrentThreadKey() {
|
||||
return std::this_thread::get_id();
|
||||
}
|
||||
|
||||
const char* GetFormatCapabilitySupportString(const FormatCapabilityCache& cache, SizeT targetIndex,
|
||||
SizeT formatIndex, FormatCapability capability) {
|
||||
if (HasFormatCapability(cache.FullCaps[targetIndex][formatIndex], capability)) return "Full";
|
||||
if (HasFormatCapability(cache.CaveatCaps[targetIndex][formatIndex], capability)) return "Caveat";
|
||||
return "None";
|
||||
}
|
||||
|
||||
SizeT GetPrintedFormatNameWidth() {
|
||||
SizeT width = 0;
|
||||
for (SizeT formatIndex = 0; formatIndex < kFormatCapabilityFormatCount; ++formatIndex) {
|
||||
const auto format = static_cast<TextureInternalFormat>(formatIndex);
|
||||
width = std::max(width, MG_Util::ConvertTextureInternalFormatToString(format).size());
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
SizeT GetCapabilityColumnWidth(FormatCapability capability) {
|
||||
SizeT width = std::strlen(GetFormatCapabilityName(capability));
|
||||
width = std::max<SizeT>(width, std::strlen("Caveat"));
|
||||
return width;
|
||||
}
|
||||
|
||||
String BuildFormatCapabilityHeader(SizeT formatNameWidth) {
|
||||
std::ostringstream line;
|
||||
line << std::left << std::setw(static_cast<Int>(formatNameWidth)) << "";
|
||||
for (FormatCapability capability : kReportedFormatCapabilities) {
|
||||
line << " | " << std::left << std::setw(static_cast<Int>(GetCapabilityColumnWidth(capability)))
|
||||
<< GetFormatCapabilityName(capability);
|
||||
}
|
||||
return line.str();
|
||||
}
|
||||
|
||||
String BuildFormatCapabilityRow(const FormatCapabilityCache& cache, SizeT targetIndex, SizeT formatIndex,
|
||||
SizeT formatNameWidth) {
|
||||
const auto format = static_cast<TextureInternalFormat>(formatIndex);
|
||||
std::ostringstream line;
|
||||
line << std::left << std::setw(static_cast<Int>(formatNameWidth))
|
||||
<< MG_Util::ConvertTextureInternalFormatToString(format);
|
||||
for (FormatCapability capability : kReportedFormatCapabilities) {
|
||||
line << " | " << std::left << std::setw(static_cast<Int>(GetCapabilityColumnWidth(capability)))
|
||||
<< GetFormatCapabilitySupportString(cache, targetIndex, formatIndex, capability);
|
||||
}
|
||||
return line.str();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void FormatCapabilityCache::Clear() {
|
||||
for (auto& row : FullCaps) {
|
||||
row.fill(FormatCapabilityFlags{});
|
||||
}
|
||||
for (auto& row : CaveatCaps) {
|
||||
row.fill(FormatCapabilityFlags{});
|
||||
}
|
||||
for (auto& row : SampleCounts) {
|
||||
for (auto& counts : row) {
|
||||
counts.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Bool HasFormatCapability(FormatCapabilityFlags caps, FormatCapability capability) {
|
||||
return static_cast<Bool>(caps & capability);
|
||||
}
|
||||
|
||||
SizeT GetFormatCapabilityTargetIndex(TextureTarget target) {
|
||||
if (target == TextureTarget::Unknown || static_cast<Int>(target) < 0 ||
|
||||
static_cast<SizeT>(target) >= kFormatCapabilityTextureTargetCount) {
|
||||
return kFormatCapabilityTargetCount;
|
||||
}
|
||||
// An external texture is a 2D one wherever a format can go.
|
||||
if (target == TextureTarget::External) return static_cast<SizeT>(TextureTarget::Texture2D);
|
||||
return static_cast<SizeT>(target);
|
||||
}
|
||||
|
||||
SizeT GetRenderbufferFormatCapabilityTargetIndex() {
|
||||
return kFormatCapabilityRenderbufferTargetIndex;
|
||||
}
|
||||
|
||||
const char* GetFormatCapabilityName(FormatCapability capability) {
|
||||
switch (capability) {
|
||||
case FormatCapability::Creatable:
|
||||
return "Creatable";
|
||||
case FormatCapability::Sampled:
|
||||
return "Sampled";
|
||||
case FormatCapability::LinearFilter:
|
||||
return "LinearFilter";
|
||||
case FormatCapability::GenerateMipmap:
|
||||
return "GenerateMipmap";
|
||||
case FormatCapability::TextureGather:
|
||||
return "TextureGather";
|
||||
case FormatCapability::TextureShadow:
|
||||
return "TextureShadow";
|
||||
case FormatCapability::FramebufferRenderable:
|
||||
return "FramebufferRenderable";
|
||||
case FormatCapability::FramebufferLayered:
|
||||
return "FramebufferLayered";
|
||||
case FormatCapability::MultisampleTexture:
|
||||
return "MultisampleTexture";
|
||||
case FormatCapability::MultisampleRenderbuffer:
|
||||
return "MultisampleRenderbuffer";
|
||||
case FormatCapability::ColorAttachment:
|
||||
return "ColorAttachment";
|
||||
case FormatCapability::DepthAttachment:
|
||||
return "DepthAttachment";
|
||||
case FormatCapability::StencilAttachment:
|
||||
return "StencilAttachment";
|
||||
case FormatCapability::TextureBuffer:
|
||||
return "TextureBuffer";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
String GetFormatCapabilityTargetName(SizeT targetIndex) {
|
||||
if (targetIndex == kFormatCapabilityRenderbufferTargetIndex) {
|
||||
return "Renderbuffer";
|
||||
}
|
||||
if (targetIndex >= kFormatCapabilityTextureTargetCount) {
|
||||
return "Unknown";
|
||||
}
|
||||
return MG_Util::ConvertTextureTargetToString(static_cast<TextureTarget>(targetIndex));
|
||||
}
|
||||
|
||||
void PrintFormatCapabilities(const FormatCapabilityCache& cache) {
|
||||
const SizeT formatNameWidth = GetPrintedFormatNameWidth();
|
||||
|
||||
MGLOG_D("Backend format capabilities:");
|
||||
for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTargetCount; ++targetIndex) {
|
||||
MGLOG_D("");
|
||||
const String targetName = GetFormatCapabilityTargetName(targetIndex);
|
||||
MGLOG_D("- %s", targetName.c_str());
|
||||
const String header = BuildFormatCapabilityHeader(formatNameWidth);
|
||||
MGLOG_D("%s", header.c_str());
|
||||
for (SizeT formatIndex = 0; formatIndex < kFormatCapabilityFormatCount; ++formatIndex) {
|
||||
const String row = BuildFormatCapabilityRow(cache, targetIndex, formatIndex, formatNameWidth);
|
||||
MGLOG_D("%s", row.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Bool BackendObject::InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (dpy == EGL_NO_DISPLAY) {
|
||||
@@ -386,20 +244,6 @@ namespace MobileGL::MG_Backend {
|
||||
m_windowHandle = {};
|
||||
}
|
||||
|
||||
namespace {
|
||||
MG_Util::Damage::Region& PresentDamageSlot() {
|
||||
thread_local MG_Util::Damage::Region damage = MG_Util::Damage::Region::Full();
|
||||
return damage;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
const MG_Util::Damage::Region& CurrentPresentDamage() {
|
||||
return PresentDamageSlot();
|
||||
}
|
||||
void SetCurrentPresentDamage(const MG_Util::Damage::Region& damage) {
|
||||
PresentDamageSlot() = damage;
|
||||
}
|
||||
|
||||
Bool BackendObject::SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
if (!m_eglDisplayInitialized || m_eglDisplay != dpy) {
|
||||
|
||||
@@ -9,238 +9,12 @@
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
#include <BackendCaps.h>
|
||||
#include "MG_State/GLState/TextureState/TextureEnum.h"
|
||||
#include <MG_Backend/BackendFunctions.h>
|
||||
#include <MG_Backend/BackendRoles.h>
|
||||
#include <MG_Util/Damage/Damage.h>
|
||||
#include <MG_Pipe/MGPipeHandles.h>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_State::GLState {
|
||||
class FramebufferObject;
|
||||
class ITextureObject;
|
||||
class RenderbufferObject;
|
||||
} // namespace MG_State::GLState
|
||||
|
||||
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;
|
||||
MG_Pipe::MGPipeHandle TextureHandle = MG_Pipe::kMGPipeNullHandle;
|
||||
MG_Pipe::MGPipeHandle RenderbufferHandle = MG_Pipe::kMGPipeNullHandle;
|
||||
|
||||
Bool IsRenderbuffer() const {
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(RenderbufferHandle)) return true;
|
||||
return Renderbuffer != nullptr;
|
||||
}
|
||||
Bool Exists() const {
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(TextureHandle)) return true;
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(RenderbufferHandle)) return true;
|
||||
return Texture != nullptr || Renderbuffer != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
// 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);
|
||||
// The by-name form: the destination is `texture`, whatever the units hold. `target` is
|
||||
// the one the record carries (the texture's own 2D-class target).
|
||||
void (*CopyTextureSubImage2D)(const SharedPtr<MG_State::GLState::ITextureObject>& texture, 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);
|
||||
// The by-name form, also used once the bound-target form has resolved its texture.
|
||||
void (*GenerateTextureMipmap)(const SharedPtr<MG_State::GLState::ITextureObject>& texture, 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);
|
||||
// The ONLY indexed query that is genuinely a backend one, and only for the pnames
|
||||
// MG_Impl/GLImpl/Getter/GL_Getter.cpp does not already own. Every indexed pname that
|
||||
// names FRONTEND state - the indexed buffer bindings, the per-unit texture/sampler
|
||||
// bindings, the image-unit bindings, the viewport rectangles, the indexed capabilities
|
||||
// - is answered in GL_Getter::GetIntegeri_v and never reaches this entry; the
|
||||
// 64-bit and float/double widths are derived there from the same answer, which is why
|
||||
// no GetInteger64i_v/GetFloati_v/GetDoublei_v table entry exists. In practice this
|
||||
// leaves GL_MAX_COMPUTE_WORK_GROUP_COUNT / _SIZE (also asked directly by
|
||||
// MG_Util/ShaderTranspiler/CompileEnv.cpp) plus whatever pname the frontend has no
|
||||
// case for at all.
|
||||
void (*GetIntegeri_v)(GLenum target, GLuint index, GLint* data);
|
||||
// There is deliberately NO GetProgramiv entry: glGetProgramiv describes the program
|
||||
// the APPLICATION wrote - link status, the transform-feedback mode, the compute local
|
||||
// size - all of which are frontend link artifacts on ProgramObject, and
|
||||
// MG_Impl/GLImpl/Program/GL_Program.cpp answers every one of them from there. Asking a
|
||||
// backend would mean asking about a DIFFERENT program (a SPIRV-Cross-generated ESSL
|
||||
// one, or a SPIR-V module), in a namespace the application never sees.
|
||||
// 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);
|
||||
};
|
||||
|
||||
enum class WindowBackend {
|
||||
Android,
|
||||
X11,
|
||||
@@ -258,208 +32,19 @@ namespace MobileGL {
|
||||
Uint32 Height = 0;
|
||||
};
|
||||
|
||||
// SHARED IMAGES (docs/archive/notes/anland/plan-ahb-dmabuf.md): server-allocated colour
|
||||
// buffers exported to clients as dma-buf descriptors.
|
||||
//
|
||||
// The server-side view of one image, as a backend sees it: `NativeBuffer` is the image's
|
||||
// AHardwareBuffer (null where there is none), `Fourcc` a DRM fourcc - ABGR8888 (R, G, B, A in
|
||||
// memory, GL RGBA8) or XBGR8888 (alpha ignored); ARGB8888 / XRGB8888 images are stored the
|
||||
// same way (SharedImageRegistry.h says why), so a backend binds every one as RGBA8.
|
||||
struct SharedImageView {
|
||||
Uint64 Id = 0;
|
||||
Uint32 Width = 0;
|
||||
Uint32 Height = 0;
|
||||
Uint32 Fourcc = 0;
|
||||
void* NativeBuffer = nullptr;
|
||||
};
|
||||
|
||||
// The client-side answer of an allocation: the image's id and its exported descriptor
|
||||
// (`Fd`, owned by the caller), with the plane layout a linux-dmabuf wl_buffer is created with.
|
||||
struct SharedImageExport {
|
||||
Uint64 Id = 0;
|
||||
int Fd = -1;
|
||||
Uint32 Width = 0;
|
||||
Uint32 Height = 0;
|
||||
Uint32 Fourcc = 0;
|
||||
Uint32 Stride = 0;
|
||||
Uint32 Offset = 0;
|
||||
Uint64 Modifier = 0;
|
||||
// A YUV image's Cb Cr plane: in the same descriptor, at this offset and pitch.
|
||||
Uint32 Plane1Stride = 0;
|
||||
Uint32 Plane1Offset = 0;
|
||||
};
|
||||
|
||||
// An import of a two-plane (YUV) dma-buf: where its planes are in the one descriptor, and
|
||||
// EGL_EXT_image_dma_buf_import's colour hints (0 = not given).
|
||||
struct SharedImageImportLayout {
|
||||
Uint32 PlaneCount = 1;
|
||||
Uint32 Offset[2] = {0, 0};
|
||||
Uint32 Pitch[2] = {0, 0};
|
||||
Uint32 ColorSpace = 0;
|
||||
Uint32 Range = 0;
|
||||
Uint32 SitingX = 0;
|
||||
Uint32 SitingY = 0;
|
||||
// The DRM format modifier the import named; HasModifier false = none (the implicit one).
|
||||
Bool HasModifier = false;
|
||||
Uint64 Modifier = 0;
|
||||
};
|
||||
|
||||
// THE DAMAGE OF THE PRESENT BEING APPLIED on this thread: the server's apply thread sets it
|
||||
// around the backend's Present (from the present record), and a backend that can hand damage
|
||||
// to its native present reads it there. Full whenever nothing set it.
|
||||
const MG_Util::Damage::Region& CurrentPresentDamage();
|
||||
void SetCurrentPresentDamage(const MG_Util::Damage::Region& damage);
|
||||
|
||||
class BackendObject {
|
||||
public:
|
||||
virtual ~BackendObject() = default;
|
||||
|
||||
// ---- shared images, SERVER side (a backend implements these) ---------------------
|
||||
//
|
||||
// Copies the default framebuffer of the surface current on this (the apply) thread into
|
||||
// `image`, rows TOP FIRST - GL's top row (y = height-1) lands in the image's first memory
|
||||
// row, which is what a wl_buffer's first row is - scaled to the image's extent if the two
|
||||
// differ. The image is ORDERED THROUGH ITS SYNC STATE (SharedImageRegistry.h): the copy
|
||||
// waits for the image's pending read fences before it writes, and its own completion
|
||||
// is published with SharedImages::PublishWrite as a sync_file - the call returns once
|
||||
// the copy is submitted, never waiting for it on the CPU (a backend with no fence export
|
||||
// waits and publishes -1). A reader binding the image waits for that fence on its GPU
|
||||
// when the write generation moved, and at its frame boundary publishes its frame's fence
|
||||
// with PublishRead / ReadTracker. The application's GL state is untouched. False = not
|
||||
// supported / failed (logged).
|
||||
//
|
||||
// `region` (GL window coordinates, origin bottom-left) is the part of the frame to copy;
|
||||
// Full copies all of it. The rest of the image is left as it is - the client sends a
|
||||
// region only for an image that already holds the frame outside it - and a backend that
|
||||
// cannot be sure the image's contents survived (a target it has never written) copies
|
||||
// all of it regardless.
|
||||
virtual Bool BlitDefaultFramebufferToSharedImage(const SharedImageView& image,
|
||||
const MG_Util::Damage::Region& region) {
|
||||
(void)image, (void)region;
|
||||
return false;
|
||||
}
|
||||
|
||||
// EGL_BUFFER_AGE_EXT of the draw surface of the context current on this (the apply)
|
||||
// thread: how many presents ago the buffer the next frame draws into held the frame then
|
||||
// presented; 0 = unknown (the application repaints everything). Asking says the client
|
||||
// relies on the answer, so from then on the backend keeps what it presents.
|
||||
//
|
||||
// `damageRegionFollows`: the client declares the region it draws (SetCurrentDamageRegion)
|
||||
// before drawing. A driver whose only buffer age is EGL_KHR_partial_update's keeps a
|
||||
// buffer's content only outside that region, so without one coming it is not asked.
|
||||
virtual Int32 QueryCurrentBufferAge(Bool damageRegionFollows) {
|
||||
(void)damageRegionFollows;
|
||||
return 0;
|
||||
}
|
||||
// eglSetDamageRegionKHR for that draw surface: the frame draws `region` (GL window
|
||||
// coordinates; Full = all of it) and nothing outside it. A backend without the notion
|
||||
// ignores it.
|
||||
virtual Bool SetCurrentDamageRegion(const MG_Util::Damage::Region& region) {
|
||||
(void)region;
|
||||
return true;
|
||||
}
|
||||
// IMPLICIT SYNC BY FLUSH (docs/archive/notes/anland/plan-x11-gpu.md): the session
|
||||
// on this (the apply) thread ends a frame at a glFlush. Every shared image it used since
|
||||
// its last boundary is published as written and read by one fence of everything it
|
||||
// submitted (SharedImages::ReadTracker::PublishFrameAsWrite), and from now on its first
|
||||
// use of an image in a frame waits for that image's pending reads (it may be about to
|
||||
// overwrite what another session still samples). A backend without shared images has
|
||||
// nothing to publish.
|
||||
virtual Bool PublishSharedImageAccesses() { return true; }
|
||||
// EGL_ANDROID_native_fence_sync's fence command, on the server: in `*fence`, a sync_file
|
||||
// (the caller's to close) that signals once everything the session submitted so far has
|
||||
// completed - and, for a session that writes images, what PublishSharedImageAccesses
|
||||
// does. -1 when there is none to give: the backend cannot export one, or the work has
|
||||
// already completed - in both cases it HAS completed when this returns.
|
||||
virtual Bool ExportNativeFence(int* fence) {
|
||||
*fence = -1;
|
||||
return PublishSharedImageAccesses();
|
||||
}
|
||||
|
||||
// ---- shared images, CLIENT side (BackendObject_Remote implements these) ----------
|
||||
//
|
||||
// A new image, allocated by the server; `out->Fd` is the caller's to close.
|
||||
virtual Bool AllocateSharedImage(Uint32 width, Uint32 height, Uint32 fourcc, SharedImageExport* out) {
|
||||
(void)width, (void)height, (void)fourcc, (void)out;
|
||||
return false;
|
||||
}
|
||||
// Names the image `fd` was exported from (the caller keeps `fd`). False: not one of this
|
||||
// server's images, or the size/format disagree.
|
||||
virtual Bool ImportSharedImage(int fd, Uint32 width, Uint32 height, Uint32 fourcc, Uint64* outId) {
|
||||
(void)fd, (void)width, (void)height, (void)fourcc, (void)outId;
|
||||
return false;
|
||||
}
|
||||
// The same for a YUV fourcc, whose planes and hints `layout` gives. A YUV buffer the
|
||||
// server did not export is taken too (its labelled CPU-copy fallback).
|
||||
virtual Bool ImportSharedImagePlanes(int fd, Uint32 width, Uint32 height, Uint32 fourcc,
|
||||
const SharedImageImportLayout& layout, Uint64* outId) {
|
||||
(void)fd, (void)width, (void)height, (void)fourcc, (void)layout, (void)outId;
|
||||
return false;
|
||||
}
|
||||
// Drops this client's reference (an allocation's or an import's).
|
||||
virtual Bool ReleaseSharedImage(Uint64 id) {
|
||||
(void)id;
|
||||
return false;
|
||||
}
|
||||
// In the GL stream: the current context's default framebuffer, copied top row first into
|
||||
// the image (BlitDefaultFramebufferToSharedImage on the server) - `region` of it, Full for
|
||||
// all. Returns when it completed.
|
||||
virtual Bool PresentToSharedImage(Uint64 id, const MG_Util::Damage::Region& region) {
|
||||
(void)id, (void)region;
|
||||
return false;
|
||||
}
|
||||
// In the GL stream: EGL_BUFFER_AGE_EXT of the current draw surface (QueryCurrentBufferAge
|
||||
// on the server). False: the backend has no answer (the caller reports 0).
|
||||
virtual Bool QueryBufferAge(Bool damageRegionFollows, EGLint* age) {
|
||||
(void)damageRegionFollows, (void)age;
|
||||
return false;
|
||||
}
|
||||
// In the GL stream: eglSetDamageRegionKHR for the current draw surface
|
||||
// (SetCurrentDamageRegion on the server).
|
||||
virtual Bool SetDamageRegion(const MG_Util::Damage::Region& region) {
|
||||
(void)region;
|
||||
return false;
|
||||
}
|
||||
// In the GL stream: the texture whose lifetime id is `textureLifetimeId` takes the image as
|
||||
// its level-0 storage (glEGLImageTargetTexture2DOES), until its next storage definition.
|
||||
// The caller has already defined level 0 as Width x Height RGBA8 with no data.
|
||||
virtual Bool AttachSharedImageToTexture(Uint64 textureLifetimeId, Uint64 id) {
|
||||
(void)textureLifetimeId, (void)id;
|
||||
return false;
|
||||
}
|
||||
// In the GL stream, from glFlush/glFinish: PublishSharedImageAccesses on the server,
|
||||
// returning once the fence is published. Skipped (true) when nothing reached the stream
|
||||
// since the last one.
|
||||
virtual Bool FlushSharedImageAccesses() { return false; }
|
||||
// In the GL stream, from eglCreateSync(EGL_SYNC_NATIVE_FENCE_ANDROID) with no descriptor:
|
||||
// ExportNativeFence on the server. `*fence` is the sync_file it handed back (the
|
||||
// caller's to close), or -1 when it had none - the fenced work has then completed.
|
||||
virtual Bool CreateNativeFence(int* fence) {
|
||||
*fence = -1;
|
||||
return false;
|
||||
}
|
||||
// The hooks only a server, or only a client of a remote server, calls (BackendRoles.h);
|
||||
// null for a backend that plays no such role.
|
||||
virtual ServerHooks* AsServer() { return nullptr; }
|
||||
virtual ClientHooks* AsClient() { return nullptr; }
|
||||
|
||||
virtual void Initialize() = 0;
|
||||
virtual Bool InitCapabilities() = 0;
|
||||
virtual Bool InitWindowSurface() = 0;
|
||||
|
||||
// A served session's apply thread - the one thread that runs this backend from here on -
|
||||
// calls this first, in a process that may serve several sessions at once. A backend whose
|
||||
// native state is process-global by construction gives this object its own copy and binds
|
||||
// it to the calling thread. The default has nothing per process to separate.
|
||||
virtual void BindSessionStateToThisThread() {}
|
||||
// A served session's apply thread asks this each time it wakes, BEFORE it applies another
|
||||
// record: true when this session's own GPU work faulted or hung the GPU and the backend has
|
||||
// latched the session for it (MGPipeSessionLatch, family DeviceLost), so the next frame -
|
||||
// which would very likely stop the GPU again - is never applied. Cheap by contract: no
|
||||
// wait, at most a status read. The default knows of no fault.
|
||||
virtual Bool LatchIfGpuFaulted() { return false; }
|
||||
// A served session's ring moved to another of its client's contexts (bind_context). A
|
||||
// backend whose surface follows the native context it binds needs nothing; one that keeps
|
||||
// a single native surface binding re-binds the surface that context was last made current
|
||||
// with. `token` is the client's context token (the EGLContext MakeEGLCurrent was given).
|
||||
virtual void OnClientContextBound(Uint64 token) { (void)token; }
|
||||
|
||||
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);
|
||||
@@ -479,36 +64,6 @@ namespace MobileGL {
|
||||
virtual void ReleaseEGLSurface(EGLSurface surface);
|
||||
virtual void ReleaseEGLResources();
|
||||
|
||||
// ---- the server's own display window going away and coming back -------------------
|
||||
//
|
||||
// `window` (a window surface's native window) is about to be destroyed - the display's
|
||||
// screen went off. Release ONLY what is bound to it: every window surface on it keeps its
|
||||
// client handle and draws into a placeholder from here on, and every context, object and
|
||||
// device stays. Called on the thread that runs this backend; nothing may reference the
|
||||
// window once it returns true. False: not supported (the caller ends the session instead).
|
||||
virtual Bool SuspendServerWindow(void* window) {
|
||||
(void)window;
|
||||
return false;
|
||||
}
|
||||
// The surfaces SuspendServerWindow parked are rebuilt on `window` (a new native window,
|
||||
// `width`x`height`), and their new extent is published as a creation publishes it. False:
|
||||
// they could not be (the caller ends the session).
|
||||
virtual Bool ResumeServerWindow(void* window, Uint32 width, Uint32 height) {
|
||||
(void)window, (void)width, (void)height;
|
||||
return false;
|
||||
}
|
||||
// `window` changed size to `width`x`height` (the display's report - the one size to publish).
|
||||
// Its surfaces publish that extent to the client now, without waiting for a present; a
|
||||
// swapchain that has to follow is rebuilt when no frame is half-recorded, else at the
|
||||
// present. False: nothing was published (no surface on it, or not supported).
|
||||
virtual Bool ServerWindowResized(void* window, Uint32 width, Uint32 height) {
|
||||
(void)window, (void)width, (void)height;
|
||||
return false;
|
||||
}
|
||||
// CLIENT side, from eglQuerySurface(EGL_WIDTH/EGL_HEIGHT): a backend whose surface extents
|
||||
// change under the application (a remote server's window) applies what it has received.
|
||||
virtual void RefreshSurfaceExtent(EGLSurface surface) { (void)surface; }
|
||||
|
||||
void SetWindowHandle(const WindowHandle& handle);
|
||||
|
||||
virtual const RendererInfo& GetRendererInfo() const = 0;
|
||||
@@ -581,7 +136,14 @@ namespace MobileGL {
|
||||
void ReleaseEGLCurrentThread(const std::thread::id& threadKey);
|
||||
};
|
||||
|
||||
// A backend's role hooks; null for no backend, or one that plays no such role.
|
||||
inline ServerHooks* ServerHooksOf(BackendObject* backend) {
|
||||
return backend != nullptr ? backend->AsServer() : nullptr;
|
||||
}
|
||||
inline ClientHooks* ClientHooksOf(BackendObject* backend) {
|
||||
return backend != nullptr ? backend->AsClient() : nullptr;
|
||||
}
|
||||
|
||||
extern UniquePtr<BackendObject>& pActiveBackendObject;
|
||||
extern GlobalBackendFunctionsTable gBackendFunctionsTable;
|
||||
} // namespace MG_Backend
|
||||
} // namespace MobileGL
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
// MobileGL - MobileGL/MG_Backend/BackendRoles.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_Backend/SharedImageTypes.h>
|
||||
#include <MG_Util/Damage/Damage.h>
|
||||
|
||||
namespace MobileGL::MG_Backend {
|
||||
// What only the server asks of the backend it applies a session's records with. A backend that
|
||||
// can serve sessions implements it and returns it from BackendObject::AsServer; each default is
|
||||
// the answer of a backend with nothing to do for that hook.
|
||||
class ServerHooks {
|
||||
public:
|
||||
// ---- shared images ------------------------------------------------------------------
|
||||
//
|
||||
// Copies the default framebuffer of the surface current on this (the apply) thread into
|
||||
// `image`, rows TOP FIRST - GL's top row (y = height-1) lands in the image's first memory
|
||||
// row, which is what a wl_buffer's first row is - scaled to the image's extent if the two
|
||||
// differ. The image is ORDERED THROUGH ITS SYNC STATE (SharedImageRegistry.h): the copy
|
||||
// waits for the image's pending read fences before it writes, and its own completion
|
||||
// is published with SharedImages::PublishWrite as a sync_file - the call returns once
|
||||
// the copy is submitted, never waiting for it on the CPU (a backend with no fence export
|
||||
// waits and publishes -1). A reader binding the image waits for that fence on its GPU
|
||||
// when the write generation moved, and at its frame boundary publishes its frame's fence
|
||||
// with PublishRead / ReadTracker. The application's GL state is untouched. False = not
|
||||
// supported / failed (logged).
|
||||
//
|
||||
// `region` (GL window coordinates, origin bottom-left) is the part of the frame to copy;
|
||||
// Full copies all of it. The rest of the image is left as it is - the client sends a
|
||||
// region only for an image that already holds the frame outside it - and a backend that
|
||||
// cannot be sure the image's contents survived (a target it has never written) copies
|
||||
// all of it regardless.
|
||||
virtual Bool BlitDefaultFramebufferToSharedImage(const SharedImageView& image,
|
||||
const MG_Util::Damage::Region& region) {
|
||||
(void)image, (void)region;
|
||||
return false;
|
||||
}
|
||||
|
||||
// EGL_BUFFER_AGE_EXT of the draw surface of the context current on this (the apply)
|
||||
// thread: how many presents ago the buffer the next frame draws into held the frame then
|
||||
// presented; 0 = unknown (the application repaints everything). Asking says the client
|
||||
// relies on the answer, so from then on the backend keeps what it presents.
|
||||
//
|
||||
// `damageRegionFollows`: the client declares the region it draws (SetCurrentDamageRegion)
|
||||
// before drawing. A driver whose only buffer age is EGL_KHR_partial_update's keeps a
|
||||
// buffer's content only outside that region, so without one coming it is not asked.
|
||||
virtual Int32 QueryCurrentBufferAge(Bool damageRegionFollows) {
|
||||
(void)damageRegionFollows;
|
||||
return 0;
|
||||
}
|
||||
// eglSetDamageRegionKHR for that draw surface: the frame draws `region` (GL window
|
||||
// coordinates; Full = all of it) and nothing outside it. A backend without the notion
|
||||
// ignores it.
|
||||
virtual Bool SetCurrentDamageRegion(const MG_Util::Damage::Region& region) {
|
||||
(void)region;
|
||||
return true;
|
||||
}
|
||||
// IMPLICIT SYNC BY FLUSH (docs/archive/notes/anland/plan-x11-gpu.md): the session
|
||||
// on this (the apply) thread ends a frame at a glFlush. Every shared image it used since
|
||||
// its last boundary is published as written and read by one fence of everything it
|
||||
// submitted (SharedImages::ReadTracker::PublishFrameAsWrite), and from now on its first
|
||||
// use of an image in a frame waits for that image's pending reads (it may be about to
|
||||
// overwrite what another session still samples). A backend without shared images has
|
||||
// nothing to publish.
|
||||
virtual Bool PublishSharedImageAccesses() { return true; }
|
||||
// EGL_ANDROID_native_fence_sync's fence command, on the server: in `*fence`, a sync_file
|
||||
// (the caller's to close) that signals once everything the session submitted so far has
|
||||
// completed - and, for a session that writes images, what PublishSharedImageAccesses
|
||||
// does. -1 when there is none to give: the backend cannot export one, or the work has
|
||||
// already completed - in both cases it HAS completed when this returns.
|
||||
virtual Bool ExportNativeFence(int* fence) {
|
||||
*fence = -1;
|
||||
return PublishSharedImageAccesses();
|
||||
}
|
||||
|
||||
// A served session's apply thread - the one thread that runs this backend from here on -
|
||||
// calls this first, in a process that may serve several sessions at once. A backend whose
|
||||
// native state is process-global by construction gives this object its own copy and binds
|
||||
// it to the calling thread. The default has nothing per process to separate.
|
||||
virtual void BindSessionStateToThisThread() {}
|
||||
// A served session's apply thread asks this each time it wakes, BEFORE it applies another
|
||||
// record: true when this session's own GPU work faulted or hung the GPU and the backend has
|
||||
// latched the session for it (MGPipeSessionLatch, family DeviceLost), so the next frame -
|
||||
// which would very likely stop the GPU again - is never applied. Cheap by contract: no
|
||||
// wait, at most a status read. The default knows of no fault.
|
||||
virtual Bool LatchIfGpuFaulted() { return false; }
|
||||
// A served session's ring moved to another of its client's contexts (bind_context). A
|
||||
// backend whose surface follows the native context it binds needs nothing; one that keeps
|
||||
// a single native surface binding re-binds the surface that context was last made current
|
||||
// with. `token` is the client's context token (the EGLContext MakeEGLCurrent was given).
|
||||
virtual void OnClientContextBound(Uint64 token) { (void)token; }
|
||||
|
||||
// ---- the server's own display window going away and coming back -------------------
|
||||
//
|
||||
// `window` (a window surface's native window) is about to be destroyed - the display's
|
||||
// screen went off. Release ONLY what is bound to it: every window surface on it keeps its
|
||||
// client handle and draws into a placeholder from here on, and every context, object and
|
||||
// device stays. Called on the thread that runs this backend; nothing may reference the
|
||||
// window once it returns true. False: not supported (the caller ends the session instead).
|
||||
virtual Bool SuspendServerWindow(void* window) {
|
||||
(void)window;
|
||||
return false;
|
||||
}
|
||||
// The surfaces SuspendServerWindow parked are rebuilt on `window` (a new native window,
|
||||
// `width`x`height`), and their new extent is published as a creation publishes it. False:
|
||||
// they could not be (the caller ends the session).
|
||||
virtual Bool ResumeServerWindow(void* window, Uint32 width, Uint32 height) {
|
||||
(void)window, (void)width, (void)height;
|
||||
return false;
|
||||
}
|
||||
// `window` changed size to `width`x`height` (the display's report - the one size to publish).
|
||||
// Its surfaces publish that extent to the client now, without waiting for a present; a
|
||||
// swapchain that has to follow is rebuilt when no frame is half-recorded, else at the
|
||||
// present. False: nothing was published (no surface on it, or not supported).
|
||||
virtual Bool ServerWindowResized(void* window, Uint32 width, Uint32 height) {
|
||||
(void)window, (void)width, (void)height;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
~ServerHooks() = default;
|
||||
};
|
||||
|
||||
// What only a client of a remote server asks of its backend. BackendObject_Remote implements it
|
||||
// and returns it from BackendObject::AsClient; each default is the answer of a backend with no
|
||||
// server behind it.
|
||||
class ClientHooks {
|
||||
public:
|
||||
// ---- shared images ------------------------------------------------------------------
|
||||
//
|
||||
// A new image, allocated by the server; `out->Fd` is the caller's to close.
|
||||
virtual Bool AllocateSharedImage(Uint32 width, Uint32 height, Uint32 fourcc, SharedImageExport* out) {
|
||||
(void)width, (void)height, (void)fourcc, (void)out;
|
||||
return false;
|
||||
}
|
||||
// Names the image `fd` was exported from (the caller keeps `fd`). False: not one of this
|
||||
// server's images, or the size/format disagree.
|
||||
virtual Bool ImportSharedImage(int fd, Uint32 width, Uint32 height, Uint32 fourcc, Uint64* outId) {
|
||||
(void)fd, (void)width, (void)height, (void)fourcc, (void)outId;
|
||||
return false;
|
||||
}
|
||||
// The same for a YUV fourcc, whose planes and hints `layout` gives. A YUV buffer the
|
||||
// server did not export is taken too (its labelled CPU-copy fallback).
|
||||
virtual Bool ImportSharedImagePlanes(int fd, Uint32 width, Uint32 height, Uint32 fourcc,
|
||||
const SharedImageImportLayout& layout, Uint64* outId) {
|
||||
(void)fd, (void)width, (void)height, (void)fourcc, (void)layout, (void)outId;
|
||||
return false;
|
||||
}
|
||||
// Drops this client's reference (an allocation's or an import's).
|
||||
virtual Bool ReleaseSharedImage(Uint64 id) {
|
||||
(void)id;
|
||||
return false;
|
||||
}
|
||||
// In the GL stream: the current context's default framebuffer, copied top row first into
|
||||
// the image (BlitDefaultFramebufferToSharedImage on the server) - `region` of it, Full for
|
||||
// all. Returns when it completed.
|
||||
virtual Bool PresentToSharedImage(Uint64 id, const MG_Util::Damage::Region& region) {
|
||||
(void)id, (void)region;
|
||||
return false;
|
||||
}
|
||||
// In the GL stream: EGL_BUFFER_AGE_EXT of the current draw surface (QueryCurrentBufferAge
|
||||
// on the server). False: the backend has no answer (the caller reports 0).
|
||||
virtual Bool QueryBufferAge(Bool damageRegionFollows, EGLint* age) {
|
||||
(void)damageRegionFollows, (void)age;
|
||||
return false;
|
||||
}
|
||||
// In the GL stream: eglSetDamageRegionKHR for the current draw surface
|
||||
// (SetCurrentDamageRegion on the server).
|
||||
virtual Bool SetDamageRegion(const MG_Util::Damage::Region& region) {
|
||||
(void)region;
|
||||
return false;
|
||||
}
|
||||
// In the GL stream: the texture whose lifetime id is `textureLifetimeId` takes the image as
|
||||
// its level-0 storage (glEGLImageTargetTexture2DOES), until its next storage definition.
|
||||
// The caller has already defined level 0 as Width x Height RGBA8 with no data.
|
||||
virtual Bool AttachSharedImageToTexture(Uint64 textureLifetimeId, Uint64 id) {
|
||||
(void)textureLifetimeId, (void)id;
|
||||
return false;
|
||||
}
|
||||
// In the GL stream, from glFlush/glFinish: PublishSharedImageAccesses on the server,
|
||||
// returning once the fence is published. Skipped (true) when nothing reached the stream
|
||||
// since the last one.
|
||||
virtual Bool FlushSharedImageAccesses() { return false; }
|
||||
// In the GL stream, from eglCreateSync(EGL_SYNC_NATIVE_FENCE_ANDROID) with no descriptor:
|
||||
// ExportNativeFence on the server. `*fence` is the sync_file it handed back (the
|
||||
// caller's to close), or -1 when it had none - the fenced work has then completed.
|
||||
virtual Bool CreateNativeFence(int* fence) {
|
||||
*fence = -1;
|
||||
return false;
|
||||
}
|
||||
// From eglQuerySurface(EGL_WIDTH/EGL_HEIGHT): a backend whose surface extents
|
||||
// change under the application (a remote server's window) applies what it has received.
|
||||
virtual void RefreshSurfaceExtent(EGLSurface surface) { (void)surface; }
|
||||
|
||||
protected:
|
||||
~ClientHooks() = default;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend
|
||||
@@ -27,10 +27,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Int ClampSamplesToBackendSupport(SizeT targetIndex, TextureInternalFormat logicalFormat, GLenum imageFormat,
|
||||
Int samples);
|
||||
|
||||
class BackendObject_DirectGLES : public BackendObject {
|
||||
class BackendObject_DirectGLES : public BackendObject, public ServerHooks {
|
||||
public:
|
||||
~BackendObject_DirectGLES() override;
|
||||
|
||||
ServerHooks* AsServer() override { return this; }
|
||||
|
||||
// The driver's verdict on this session's context (DirectGLES::LatchIfDeviceLost).
|
||||
Bool LatchIfGpuFaulted() override;
|
||||
void Initialize() override;
|
||||
|
||||
@@ -150,7 +150,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// `debugKnob` false: the knob is not consulted here (the on-screen Present, so an injected loss
|
||||
// lands on an offscreen client's shared-image present or readback, never on the compositor's).
|
||||
Bool LatchIfDeviceLost(const char* site, Bool debugKnob = true);
|
||||
// The apply loop's form of it (BackendObject::LatchIfGpuFaulted), asked each time the apply thread
|
||||
// The apply loop's form of it (ServerHooks::LatchIfGpuFaulted), asked each time the apply thread
|
||||
// wakes: the driver's report only, no debug knob, and no EGL ground-truth verification spent.
|
||||
Bool LatchIfGuiltyBeforeApply();
|
||||
#endif
|
||||
@@ -536,7 +536,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image) on what the active unit has bound to
|
||||
// GL_TEXTURE_2D. False when the driver refused it (its GL error is consumed).
|
||||
Bool TargetBoundTexture2D(const EglImageRef& image);
|
||||
// BackendObject::BlitDefaultFramebufferToSharedImage, for the calling thread's context.
|
||||
// ServerHooks::BlitDefaultFramebufferToSharedImage, for the calling thread's context.
|
||||
Bool BlitDefaultFramebufferTo(const SharedImageView& image, const MG_Util::Damage::Region& region);
|
||||
|
||||
// THE READER'S HALF of the image's sync state (SharedImageRegistry.h). Called on every use
|
||||
@@ -556,12 +556,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// at Present and wherever the current native context stops being current (its reads can
|
||||
// only be fenced from it). Nothing noted = nothing done.
|
||||
void PublishPendingReads();
|
||||
// IMPLICIT SYNC BY FLUSH (BackendObject::PublishSharedImageAccesses): everything the
|
||||
// IMPLICIT SYNC BY FLUSH (ServerHooks::PublishSharedImageAccesses): everything the
|
||||
// calling session used since its last boundary is published as written and read by one
|
||||
// fence, and the session's first use of an image in each frame from now on also waits for
|
||||
// the image's pending reads.
|
||||
Bool PublishPendingAccesses();
|
||||
// EGL_ANDROID_native_fence_sync's fence command (BackendObject::ExportNativeFence): a
|
||||
// EGL_ANDROID_native_fence_sync's fence command (ServerHooks::ExportNativeFence): a
|
||||
// sync_file of everything the current context submitted in `*fence` (-1: none, and the work
|
||||
// is waited out here), and - for a session that writes images - the boundary
|
||||
// PublishPendingAccesses makes.
|
||||
|
||||
@@ -20,11 +20,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
PFN_vkGetPhysicalDeviceFormatProperties getFormatProperties,
|
||||
const MG_External::VulkanCapabilities& capabilities, FormatCapabilityCache& cache);
|
||||
|
||||
class BackendObject_DirectVulkan : public BackendObject {
|
||||
class BackendObject_DirectVulkan : public BackendObject, public ServerHooks {
|
||||
public:
|
||||
BackendObject_DirectVulkan();
|
||||
~BackendObject_DirectVulkan() override;
|
||||
|
||||
ServerHooks* AsServer() override { return this; }
|
||||
|
||||
void Initialize() override;
|
||||
void BindSessionStateToThisThread() override;
|
||||
// The GPU hang watch's verdict on this session's device (VulkanRenderer::LatchIfGpuHung).
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
#include <MG_Backend/BackendObject.h>
|
||||
#include <MG_Backend/BackendFunctions.h>
|
||||
#include "MagmaSession.h"
|
||||
#include "Renderer/VulkanRenderer.h"
|
||||
|
||||
|
||||
@@ -214,7 +214,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void* getAhbProperties = nullptr; // PFN_vkGetAndroidHardwareBufferPropertiesANDROID
|
||||
};
|
||||
Bool GetWireAhbImport(WireAhbImport& out) const;
|
||||
// SHARED IMAGES (BackendObject::BlitDefaultFramebufferToSharedImage): the active surface
|
||||
// SHARED IMAGES (ServerHooks::BlitDefaultFramebufferToSharedImage): the active surface
|
||||
// target's default framebuffer, copied into `image` top row first and scaled to its
|
||||
// extent. Returns once the copy is submitted, with its fence published to the image
|
||||
// (SharedImages::PublishWrite); waits for it on the CPU only on a device without sync_file
|
||||
@@ -225,12 +225,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// renderer has written before - the rest of it is left as it is; a first write, a scaled
|
||||
// or turned copy copies all of it.
|
||||
Bool BlitDefaultFramebufferToSharedImage(const SharedImageView& image, const MG_Util::Damage::Region& region);
|
||||
// IMPLICIT SYNC BY FLUSH (BackendObject::PublishSharedImageAccesses): every image acquired
|
||||
// IMPLICIT SYNC BY FLUSH (ServerHooks::PublishSharedImageAccesses): every image acquired
|
||||
// since the last boundary goes back to the foreign family, the recording is submitted with
|
||||
// the exportable semaphore, and its sync_file is published to each as a write and a read.
|
||||
// From then on an image's first acquire per frame also waits for its pending reads.
|
||||
Bool PublishSharedImageAccesses();
|
||||
// EGL_ANDROID_native_fence_sync's fence command (BackendObject::ExportNativeFence): the
|
||||
// EGL_ANDROID_native_fence_sync's fence command (ServerHooks::ExportNativeFence): the
|
||||
// submission's sync_file in `*fence` (-1: none, and the work is waited out here), and - for
|
||||
// a session that writes images - the boundary PublishSharedImageAccesses makes.
|
||||
Bool ExportNativeFence(int* fence);
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
// MobileGL - MobileGL/MG_Backend/FormatCapabilities.cpp
|
||||
// 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
|
||||
|
||||
#include <BackendCaps.h>
|
||||
#include "MG_Util/Converters/MGToStr/TextureEnumConverter.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
namespace MobileGL::MG_Backend {
|
||||
namespace {
|
||||
const char* GetFormatCapabilitySupportString(const FormatCapabilityCache& cache, SizeT targetIndex,
|
||||
SizeT formatIndex, FormatCapability capability) {
|
||||
if (HasFormatCapability(cache.FullCaps[targetIndex][formatIndex], capability)) return "Full";
|
||||
if (HasFormatCapability(cache.CaveatCaps[targetIndex][formatIndex], capability)) return "Caveat";
|
||||
return "None";
|
||||
}
|
||||
|
||||
SizeT GetPrintedFormatNameWidth() {
|
||||
SizeT width = 0;
|
||||
for (SizeT formatIndex = 0; formatIndex < kFormatCapabilityFormatCount; ++formatIndex) {
|
||||
const auto format = static_cast<TextureInternalFormat>(formatIndex);
|
||||
width = std::max(width, MG_Util::ConvertTextureInternalFormatToString(format).size());
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
SizeT GetCapabilityColumnWidth(FormatCapability capability) {
|
||||
SizeT width = std::strlen(GetFormatCapabilityName(capability));
|
||||
width = std::max<SizeT>(width, std::strlen("Caveat"));
|
||||
return width;
|
||||
}
|
||||
|
||||
String BuildFormatCapabilityHeader(SizeT formatNameWidth) {
|
||||
std::ostringstream line;
|
||||
line << std::left << std::setw(static_cast<Int>(formatNameWidth)) << "";
|
||||
for (FormatCapability capability : kReportedFormatCapabilities) {
|
||||
line << " | " << std::left << std::setw(static_cast<Int>(GetCapabilityColumnWidth(capability)))
|
||||
<< GetFormatCapabilityName(capability);
|
||||
}
|
||||
return line.str();
|
||||
}
|
||||
|
||||
String BuildFormatCapabilityRow(const FormatCapabilityCache& cache, SizeT targetIndex, SizeT formatIndex,
|
||||
SizeT formatNameWidth) {
|
||||
const auto format = static_cast<TextureInternalFormat>(formatIndex);
|
||||
std::ostringstream line;
|
||||
line << std::left << std::setw(static_cast<Int>(formatNameWidth))
|
||||
<< MG_Util::ConvertTextureInternalFormatToString(format);
|
||||
for (FormatCapability capability : kReportedFormatCapabilities) {
|
||||
line << " | " << std::left << std::setw(static_cast<Int>(GetCapabilityColumnWidth(capability)))
|
||||
<< GetFormatCapabilitySupportString(cache, targetIndex, formatIndex, capability);
|
||||
}
|
||||
return line.str();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void FormatCapabilityCache::Clear() {
|
||||
for (auto& row : FullCaps) {
|
||||
row.fill(FormatCapabilityFlags{});
|
||||
}
|
||||
for (auto& row : CaveatCaps) {
|
||||
row.fill(FormatCapabilityFlags{});
|
||||
}
|
||||
for (auto& row : SampleCounts) {
|
||||
for (auto& counts : row) {
|
||||
counts.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Bool HasFormatCapability(FormatCapabilityFlags caps, FormatCapability capability) {
|
||||
return static_cast<Bool>(caps & capability);
|
||||
}
|
||||
|
||||
SizeT GetFormatCapabilityTargetIndex(TextureTarget target) {
|
||||
if (target == TextureTarget::Unknown || static_cast<Int>(target) < 0 ||
|
||||
static_cast<SizeT>(target) >= kFormatCapabilityTextureTargetCount) {
|
||||
return kFormatCapabilityTargetCount;
|
||||
}
|
||||
// An external texture is a 2D one wherever a format can go.
|
||||
if (target == TextureTarget::External) return static_cast<SizeT>(TextureTarget::Texture2D);
|
||||
return static_cast<SizeT>(target);
|
||||
}
|
||||
|
||||
SizeT GetRenderbufferFormatCapabilityTargetIndex() {
|
||||
return kFormatCapabilityRenderbufferTargetIndex;
|
||||
}
|
||||
|
||||
const char* GetFormatCapabilityName(FormatCapability capability) {
|
||||
switch (capability) {
|
||||
case FormatCapability::Creatable:
|
||||
return "Creatable";
|
||||
case FormatCapability::Sampled:
|
||||
return "Sampled";
|
||||
case FormatCapability::LinearFilter:
|
||||
return "LinearFilter";
|
||||
case FormatCapability::GenerateMipmap:
|
||||
return "GenerateMipmap";
|
||||
case FormatCapability::TextureGather:
|
||||
return "TextureGather";
|
||||
case FormatCapability::TextureShadow:
|
||||
return "TextureShadow";
|
||||
case FormatCapability::FramebufferRenderable:
|
||||
return "FramebufferRenderable";
|
||||
case FormatCapability::FramebufferLayered:
|
||||
return "FramebufferLayered";
|
||||
case FormatCapability::MultisampleTexture:
|
||||
return "MultisampleTexture";
|
||||
case FormatCapability::MultisampleRenderbuffer:
|
||||
return "MultisampleRenderbuffer";
|
||||
case FormatCapability::ColorAttachment:
|
||||
return "ColorAttachment";
|
||||
case FormatCapability::DepthAttachment:
|
||||
return "DepthAttachment";
|
||||
case FormatCapability::StencilAttachment:
|
||||
return "StencilAttachment";
|
||||
case FormatCapability::TextureBuffer:
|
||||
return "TextureBuffer";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
String GetFormatCapabilityTargetName(SizeT targetIndex) {
|
||||
if (targetIndex == kFormatCapabilityRenderbufferTargetIndex) {
|
||||
return "Renderbuffer";
|
||||
}
|
||||
if (targetIndex >= kFormatCapabilityTextureTargetCount) {
|
||||
return "Unknown";
|
||||
}
|
||||
return MG_Util::ConvertTextureTargetToString(static_cast<TextureTarget>(targetIndex));
|
||||
}
|
||||
|
||||
void PrintFormatCapabilities(const FormatCapabilityCache& cache) {
|
||||
const SizeT formatNameWidth = GetPrintedFormatNameWidth();
|
||||
|
||||
MGLOG_D("Backend format capabilities:");
|
||||
for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTargetCount; ++targetIndex) {
|
||||
MGLOG_D("");
|
||||
const String targetName = GetFormatCapabilityTargetName(targetIndex);
|
||||
MGLOG_D("- %s", targetName.c_str());
|
||||
const String header = BuildFormatCapabilityHeader(formatNameWidth);
|
||||
MGLOG_D("%s", header.c_str());
|
||||
for (SizeT formatIndex = 0; formatIndex < kFormatCapabilityFormatCount; ++formatIndex) {
|
||||
const String row = BuildFormatCapabilityRow(cache, targetIndex, formatIndex, formatNameWidth);
|
||||
MGLOG_D("%s", row.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend
|
||||
@@ -0,0 +1,58 @@
|
||||
// MobileGL - MobileGL/MG_Backend/SharedImageTypes.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>
|
||||
|
||||
namespace MobileGL::MG_Backend {
|
||||
// SHARED IMAGES (docs/archive/notes/anland/plan-ahb-dmabuf.md): server-allocated colour
|
||||
// buffers exported to clients as dma-buf descriptors.
|
||||
//
|
||||
// The server-side view of one image, as a backend sees it: `NativeBuffer` is the image's
|
||||
// AHardwareBuffer (null where there is none), `Fourcc` a DRM fourcc - ABGR8888 (R, G, B, A in
|
||||
// memory, GL RGBA8) or XBGR8888 (alpha ignored); ARGB8888 / XRGB8888 images are stored the
|
||||
// same way (SharedImageRegistry.h says why), so a backend binds every one as RGBA8.
|
||||
struct SharedImageView {
|
||||
Uint64 Id = 0;
|
||||
Uint32 Width = 0;
|
||||
Uint32 Height = 0;
|
||||
Uint32 Fourcc = 0;
|
||||
void* NativeBuffer = nullptr;
|
||||
};
|
||||
|
||||
// The client-side answer of an allocation: the image's id and its exported descriptor
|
||||
// (`Fd`, owned by the caller), with the plane layout a linux-dmabuf wl_buffer is created with.
|
||||
struct SharedImageExport {
|
||||
Uint64 Id = 0;
|
||||
int Fd = -1;
|
||||
Uint32 Width = 0;
|
||||
Uint32 Height = 0;
|
||||
Uint32 Fourcc = 0;
|
||||
Uint32 Stride = 0;
|
||||
Uint32 Offset = 0;
|
||||
Uint64 Modifier = 0;
|
||||
// A YUV image's Cb Cr plane: in the same descriptor, at this offset and pitch.
|
||||
Uint32 Plane1Stride = 0;
|
||||
Uint32 Plane1Offset = 0;
|
||||
};
|
||||
|
||||
// An import of a two-plane (YUV) dma-buf: where its planes are in the one descriptor, and
|
||||
// EGL_EXT_image_dma_buf_import's colour hints (0 = not given).
|
||||
struct SharedImageImportLayout {
|
||||
Uint32 PlaneCount = 1;
|
||||
Uint32 Offset[2] = {0, 0};
|
||||
Uint32 Pitch[2] = {0, 0};
|
||||
Uint32 ColorSpace = 0;
|
||||
Uint32 Range = 0;
|
||||
Uint32 SitingX = 0;
|
||||
Uint32 SitingY = 0;
|
||||
// The DRM format modifier the import named; HasModifier false = none (the implicit one).
|
||||
Bool HasModifier = false;
|
||||
Uint64 Modifier = 0;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend
|
||||
@@ -1194,8 +1194,9 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
}
|
||||
if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) {
|
||||
// The display's dma-buf EGLImages went with it; so do their shared-image references.
|
||||
MG_Backend::ClientHooks* client = backendObject->AsClient();
|
||||
for (const Uint64 sharedImageId : state->TakeOrphanedSharedImages()) {
|
||||
(void)backendObject->ReleaseSharedImage(sharedImageId);
|
||||
if (client != nullptr) (void)client->ReleaseSharedImage(sharedImageId);
|
||||
}
|
||||
backendObject->ReleaseEGLResources();
|
||||
}
|
||||
@@ -1305,9 +1306,9 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
if (StreamGateActive()) StreamBindCallingThreadLocked();
|
||||
#endif
|
||||
EGLint age = 0;
|
||||
auto* backendObject = MG_Backend::pActiveBackendObject.get();
|
||||
MG_Backend::ClientHooks* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get());
|
||||
const Bool regionFollows = DamageRegionSurfaces().count(surface) != 0;
|
||||
if (backendObject == nullptr || !backendObject->QueryBufferAge(regionFollows, &age)) age = 0;
|
||||
if (client == nullptr || !client->QueryBufferAge(regionFollows, &age)) age = 0;
|
||||
*value = age;
|
||||
return EGL_TRUE;
|
||||
}
|
||||
@@ -1315,8 +1316,8 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
// backend: let it bring the surface's record up to date first, so a caller that polls the size
|
||||
// while it draws nothing still sees the change.
|
||||
if (attribute == EGL_WIDTH || attribute == EGL_HEIGHT) {
|
||||
if (auto* backendObject = MG_Backend::pActiveBackendObject.get())
|
||||
backendObject->RefreshSurfaceExtent(surface);
|
||||
if (auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get()))
|
||||
client->RefreshSurfaceExtent(surface);
|
||||
}
|
||||
return state->QuerySurface(display, surface, attribute, value) ? EGL_TRUE : EGL_FALSE;
|
||||
}
|
||||
@@ -1358,7 +1359,8 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
if (StreamGateActive()) StreamBindCallingThreadLocked();
|
||||
#endif
|
||||
DamageRegionSurfaces().insert(surface);
|
||||
if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) (void)backendObject->SetDamageRegion(region);
|
||||
if (auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get()))
|
||||
(void)client->SetDamageRegion(region);
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
@@ -1618,8 +1620,8 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
{
|
||||
const GLStreamScope stream;
|
||||
MG_Pipe::MGPipeClientDrainTextureUploads();
|
||||
auto* backendObject = MG_Backend::pActiveBackendObject.get();
|
||||
if (backendObject == nullptr || !backendObject->CreateNativeFence(&fence)) {
|
||||
auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get());
|
||||
if (client == nullptr || !client->CreateNativeFence(&fence)) {
|
||||
MGLOG_E_ONCE("eglCreateSync: the server did not answer a native fence; none is created");
|
||||
state->SetError(EGL_BAD_ALLOC);
|
||||
return EGL_NO_SYNC;
|
||||
@@ -1690,13 +1692,14 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
if (known >= 0) return known != 0;
|
||||
auto* backendObject = MG_Backend::pActiveBackendObject.get();
|
||||
if (backendObject == nullptr) return false;
|
||||
MG_Backend::ClientHooks* client = backendObject->AsClient();
|
||||
MG_Backend::SharedImageExport probe{};
|
||||
const Bool allocated =
|
||||
backendObject->AllocateSharedImage(16, 16, MG_State::EGLState::EGLContext::kDrmFourccNv12, &probe);
|
||||
const Bool allocated = client != nullptr && client->AllocateSharedImage(
|
||||
16, 16, MG_State::EGLState::EGLContext::kDrmFourccNv12, &probe);
|
||||
#if !defined(_WIN32)
|
||||
if (probe.Fd >= 0) ::close(probe.Fd);
|
||||
#endif
|
||||
if (allocated && probe.Id != 0) (void)backendObject->ReleaseSharedImage(probe.Id);
|
||||
if (allocated && probe.Id != 0) (void)client->ReleaseSharedImage(probe.Id);
|
||||
answer.store(allocated ? 1 : 0, std::memory_order_release);
|
||||
MGLOG_I("Shared images: the server %s YUV (NV12) images", allocated ? "holds" : "has no");
|
||||
return allocated;
|
||||
@@ -1751,7 +1754,8 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
// Uploads still waiting for a validate point go first: they are part of what is published.
|
||||
MG_Pipe::MGPipeClientDrainTextureUploads();
|
||||
#endif
|
||||
if (backendObject != nullptr && !backendObject->FlushSharedImageAccesses()) {
|
||||
MG_Backend::ClientHooks* client = MG_Backend::ClientHooksOf(backendObject);
|
||||
if (backendObject != nullptr && (client == nullptr || !client->FlushSharedImageAccesses())) {
|
||||
MGLOG_E_ONCE("glFlush: this session's shared-image accesses could not be published; another session may "
|
||||
"read them early");
|
||||
}
|
||||
@@ -1778,7 +1782,9 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
if (!state->PrepareDmaBufImport(dpy, ctx, buffer, attrib_list, &request)) {
|
||||
return EGL_NO_IMAGE;
|
||||
}
|
||||
auto* backendObject = GetBackendObject(state);
|
||||
// GetBackendObject reports the missing backend; a backend that is no remote server's client
|
||||
// imports nothing.
|
||||
MG_Backend::ClientHooks* client = MG_Backend::ClientHooksOf(GetBackendObject(state));
|
||||
Uint64 sharedImageId = 0;
|
||||
Bool imported = false;
|
||||
if (MG_State::EGLState::EGLContext::IsDmaBufFourccYuv(request.Fourcc)) {
|
||||
@@ -1804,14 +1810,13 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
layout.SitingY = request.SitingY;
|
||||
layout.HasModifier = request.HasModifier;
|
||||
layout.Modifier = request.Modifier;
|
||||
imported = backendObject != nullptr &&
|
||||
backendObject->ImportSharedImagePlanes(request.Fd, static_cast<Uint32>(request.Width),
|
||||
static_cast<Uint32>(request.Height), request.Fourcc,
|
||||
layout, &sharedImageId);
|
||||
} else {
|
||||
imported =
|
||||
backendObject != nullptr &&
|
||||
backendObject->ImportSharedImage(request.Fd, static_cast<Uint32>(request.Width),
|
||||
client != nullptr && client->ImportSharedImagePlanes(request.Fd, static_cast<Uint32>(request.Width),
|
||||
static_cast<Uint32>(request.Height),
|
||||
request.Fourcc, layout, &sharedImageId);
|
||||
} else {
|
||||
imported = client != nullptr &&
|
||||
client->ImportSharedImage(request.Fd, static_cast<Uint32>(request.Width),
|
||||
static_cast<Uint32>(request.Height), request.Fourcc, &sharedImageId);
|
||||
}
|
||||
if (!imported || sharedImageId == 0) {
|
||||
@@ -1831,7 +1836,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
.Height = request.Height,
|
||||
.Fourcc = request.Fourcc,
|
||||
.WireEpoch = wireEpoch});
|
||||
if (image == EGL_NO_IMAGE) (void)backendObject->ReleaseSharedImage(sharedImageId);
|
||||
if (image == EGL_NO_IMAGE) (void)client->ReleaseSharedImage(sharedImageId);
|
||||
return image;
|
||||
}
|
||||
|
||||
@@ -1857,8 +1862,8 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
// A texture the image was bound to keeps its own reference on the server (EGLImage
|
||||
// siblings outlive the image), so only this handle's goes.
|
||||
if (sharedImageId != 0) {
|
||||
if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) {
|
||||
(void)backendObject->ReleaseSharedImage(sharedImageId);
|
||||
if (auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get())) {
|
||||
(void)client->ReleaseSharedImage(sharedImageId);
|
||||
}
|
||||
}
|
||||
return EGL_TRUE;
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
EGLBoolean QueryDmaBufFormats(EGLDisplay dpy, EGLint max_formats, EGLint* formats, EGLint* num_formats);
|
||||
EGLBoolean QueryDmaBufModifiers(EGLDisplay dpy, EGLint format, EGLint max_modifiers, EGLuint64KHR* modifiers,
|
||||
EGLBoolean* external_only, EGLint* num_modifiers);
|
||||
// Whether the active backend has shared images (MG_Backend::BackendObject::AllocateSharedImage):
|
||||
// Whether the active backend has shared images (MG_Backend::ClientHooks::AllocateSharedImage):
|
||||
// a split client's does, a monolith's does not. Decides the dma-buf EGL extensions, the
|
||||
// Wayland linux-dmabuf presentation and GL_OES_EGL_image.
|
||||
Bool SharedImagesAvailable();
|
||||
|
||||
@@ -470,8 +470,8 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland {
|
||||
api.marshalFlags(buffer.buffer, kBufferDestroy, nullptr, api.getVersion(buffer.buffer),
|
||||
kMarshalFlagDestroy);
|
||||
if (buffer.imageId != 0) {
|
||||
if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) {
|
||||
(void)backendObject->ReleaseSharedImage(buffer.imageId);
|
||||
if (auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get())) {
|
||||
(void)client->ReleaseSharedImage(buffer.imageId);
|
||||
}
|
||||
}
|
||||
buffer = DmabufBuffer{};
|
||||
@@ -487,15 +487,16 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland {
|
||||
Release(buffer);
|
||||
auto* backendObject = MG_Backend::pActiveBackendObject.get();
|
||||
if (protocol == nullptr || dmabuf == nullptr || backendObject == nullptr) return false;
|
||||
MG_Backend::ClientHooks* client = backendObject->AsClient();
|
||||
MG_Backend::SharedImageExport image;
|
||||
if (!backendObject->AllocateSharedImage(static_cast<Uint32>(width), static_cast<Uint32>(height), fourcc,
|
||||
&image)) {
|
||||
if (client == nullptr ||
|
||||
!client->AllocateSharedImage(static_cast<Uint32>(width), static_cast<Uint32>(height), fourcc, &image)) {
|
||||
MGLOG_E("Wayland: the server allocated no %dx%d shared image", width, height);
|
||||
return false;
|
||||
}
|
||||
const auto dropImage = [&] {
|
||||
if (image.Fd >= 0) close(image.Fd);
|
||||
(void)backendObject->ReleaseSharedImage(image.Id);
|
||||
(void)client->ReleaseSharedImage(image.Id);
|
||||
};
|
||||
if (image.Fd < 0 || image.Width != static_cast<Uint32>(width) ||
|
||||
image.Height != static_cast<Uint32>(height)) {
|
||||
@@ -871,7 +872,8 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland {
|
||||
const SizeT index = static_cast<SizeT>(buffer - impl.dmabufBuffers);
|
||||
const MG_Util::Damage::Region copy =
|
||||
impl.dmabufDamage.TakeForWrite(index, damage, impl.width, impl.height);
|
||||
if (backendObject->PresentToSharedImage(buffer->imageId, copy)) {
|
||||
MG_Backend::ClientHooks* client = backendObject->AsClient();
|
||||
if (client != nullptr && client->PresentToSharedImage(buffer->imageId, copy)) {
|
||||
impl.Commit(buffer->buffer, damage);
|
||||
buffer->busy = true;
|
||||
return PresentResult::kPresented;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// End of Source File Header
|
||||
|
||||
#include "GL_Sync.h"
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include <MG_Backend/BackendFunctions.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_State/GLState/BufferState/BufferObject.h>
|
||||
#include <MG_Impl/Pipe/PipeFill.h>
|
||||
|
||||
@@ -6219,9 +6219,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
if (unpackBuffer != 0) BindBuffer(GL_PIXEL_UNPACK_BUFFER, static_cast<GLuint>(unpackBuffer));
|
||||
}
|
||||
|
||||
auto* backendObject = MG_Backend::pActiveBackendObject.get();
|
||||
if (backendObject == nullptr ||
|
||||
!backendObject->AttachSharedImageToTexture(textureObject->GetLifetimeId(), sharedImageId)) {
|
||||
auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get());
|
||||
if (client == nullptr || !client->AttachSharedImageToTexture(textureObject->GetLifetimeId(), sharedImageId)) {
|
||||
MGLOG_E_ONCE("glEGLImageTargetTexture2DOES: shared image %llu could not be bound to texture %u",
|
||||
static_cast<unsigned long long>(sharedImageId), textureObject->GetExternalIndex());
|
||||
MG_State::pGLContext->RecordError(
|
||||
|
||||
@@ -249,9 +249,9 @@ namespace MobileGL::MG_Impl::GLXImpl {
|
||||
class BackendImageSource final : public X11Present::ImageSource {
|
||||
public:
|
||||
Bool Allocate(Uint32 width, Uint32 height, Uint32 fourcc, X11Present::Image* out) override {
|
||||
auto* backend = MG_Backend::pActiveBackendObject.get();
|
||||
auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get());
|
||||
MG_Backend::SharedImageExport image;
|
||||
if (backend == nullptr || !backend->AllocateSharedImage(width, height, fourcc, &image)) return false;
|
||||
if (client == nullptr || !client->AllocateSharedImage(width, height, fourcc, &image)) return false;
|
||||
out->Id = image.Id;
|
||||
out->Fd = image.Fd;
|
||||
out->Width = image.Width;
|
||||
@@ -263,11 +263,12 @@ namespace MobileGL::MG_Impl::GLXImpl {
|
||||
return true;
|
||||
}
|
||||
void Release(Uint64 id) override {
|
||||
if (auto* backend = MG_Backend::pActiveBackendObject.get()) (void)backend->ReleaseSharedImage(id);
|
||||
if (auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get()))
|
||||
(void)client->ReleaseSharedImage(id);
|
||||
}
|
||||
Bool CopyFrame(Uint64 id, const MG_Util::Damage::Region& region) override {
|
||||
auto* backend = MG_Backend::pActiveBackendObject.get();
|
||||
return backend != nullptr && backend->PresentToSharedImage(id, region);
|
||||
auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get());
|
||||
return client != nullptr && client->PresentToSharedImage(id, region);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
|
||||
#include <MG_Backend/BackendObject.h>
|
||||
#include <MG_Backend/BackendFunctions.h>
|
||||
#include <MG_Pipe/MGPipeTypes.h>
|
||||
#include <MG_Pipe/MGPipeValueTypes.h>
|
||||
#include <MG_Pipe/PipeVerbSink.h>
|
||||
|
||||
@@ -86,8 +86,9 @@ MOBILEGL_EXPORT int mobilegl_shared_image_allocate(uint32_t width, uint32_t heig
|
||||
const std::lock_guard<std::mutex> lock(g_apiMutex);
|
||||
BackendObject* backend = ActiveBackend(true);
|
||||
if (backend == nullptr) return -EIO;
|
||||
MobileGL::MG_Backend::ClientHooks* client = backend->AsClient();
|
||||
MobileGL::MG_Backend::SharedImageExport image{};
|
||||
if (!backend->AllocateSharedImage(width, height, fourcc, &image) || image.Fd < 0) {
|
||||
if (client == nullptr || !client->AllocateSharedImage(width, height, fourcc, &image) || image.Fd < 0) {
|
||||
if (image.Fd >= 0) ::close(image.Fd);
|
||||
return -EIO;
|
||||
}
|
||||
@@ -115,8 +116,9 @@ MOBILEGL_EXPORT int mobilegl_shared_image_import(int fd, uint32_t width, uint32_
|
||||
const std::lock_guard<std::mutex> lock(g_apiMutex);
|
||||
BackendObject* backend = ActiveBackend(true);
|
||||
if (backend == nullptr) return -EIO;
|
||||
MobileGL::MG_Backend::ClientHooks* client = backend->AsClient();
|
||||
uint64_t id = 0;
|
||||
if (!backend->ImportSharedImage(fd, width, height, fourcc, &id)) return -EIO;
|
||||
if (client == nullptr || !client->ImportSharedImage(fd, width, height, fourcc, &id)) return -EIO;
|
||||
*out_id = id;
|
||||
return 0;
|
||||
}
|
||||
@@ -130,6 +132,7 @@ MOBILEGL_EXPORT int mobilegl_shared_image_import_planes(int fd, uint32_t width,
|
||||
const std::lock_guard<std::mutex> lock(g_apiMutex);
|
||||
BackendObject* backend = ActiveBackend(true);
|
||||
if (backend == nullptr) return -EIO;
|
||||
MobileGL::MG_Backend::ClientHooks* client = backend->AsClient();
|
||||
MobileGL::MG_Backend::SharedImageImportLayout layout;
|
||||
layout.PlaneCount = 2;
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
@@ -140,7 +143,7 @@ MOBILEGL_EXPORT int mobilegl_shared_image_import_planes(int fd, uint32_t width,
|
||||
layout.HasModifier = planes->modifier != kModifierInvalid;
|
||||
layout.Modifier = planes->modifier;
|
||||
uint64_t id = 0;
|
||||
if (!backend->ImportSharedImagePlanes(fd, width, height, fourcc, layout, &id)) return -EIO;
|
||||
if (client == nullptr || !client->ImportSharedImagePlanes(fd, width, height, fourcc, layout, &id)) return -EIO;
|
||||
*out_id = id;
|
||||
return 0;
|
||||
}
|
||||
@@ -153,5 +156,6 @@ MOBILEGL_EXPORT int mobilegl_shared_image_release(uint64_t id) {
|
||||
const std::lock_guard<std::mutex> lock(g_apiMutex);
|
||||
BackendObject* backend = ActiveBackend(false);
|
||||
if (backend == nullptr) return -ENODEV;
|
||||
return backend->ReleaseSharedImage(id) ? 0 : -EIO;
|
||||
MobileGL::MG_Backend::ClientHooks* client = backend->AsClient();
|
||||
return client != nullptr && client->ReleaseSharedImage(id) ? 0 : -EIO;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
// the pixels the draw produced or the case fails.
|
||||
//
|
||||
// 3. THE FIRST CASE ENDS WITH EndFrame(), DELIBERATELY. `Present` has ZERO call sites in
|
||||
// MG_Impl - it is reached only through EGLImpl.cpp:178 -> BackendObject.cpp:396 - so a
|
||||
// MG_Impl - it is reached only through eglSwapBuffers -> BackendObject::SwapEGLBuffers - so a
|
||||
// scenario that never swaps never touches it, and B would then exercise a STRICT SUBSET of
|
||||
// what ClearThenReadPixelsScenario already covers. BRIEF-P5 4.B lists Present(67) among the
|
||||
// catalogue rows this target needs, so the frame boundary is here on purpose: B is A's slot
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// gen_pipe.py's block regexes end at a blank line: keep the empty line after each macro.
|
||||
// clang-format off
|
||||
|
||||
// X(Verb, Class) - one row per function-pointer member of MG_Backend::GLFunctionsTable (BackendObject.h),
|
||||
// X(Verb, Class) - one row per function-pointer member of MG_Backend::GLFunctionsTable (BackendFunctions.h),
|
||||
// in declaration order. gen_pipe.py parses that struct and refuses a row set that is not exactly its member set.
|
||||
#define MGP_FILL_VERB_LIST(X) \
|
||||
X(DrawArrays, kDraw) \
|
||||
|
||||
@@ -1602,7 +1602,7 @@ namespace MobileGL::MG_Pipe {
|
||||
MGP_ASSERT_POD(MGPStreamOutputBind, 16);
|
||||
|
||||
// set_storage_block_binding = glShaderStorageBlockBinding (P5b i1). The block is named,
|
||||
// not indexed: BackendObject.h says why - the application's index is the frontend
|
||||
// not indexed: BackendFunctions.h says why - the application's index is the frontend
|
||||
// interface-query enumeration's and no backend shares that index space, the NAME is the
|
||||
// one coordinate all three agree on. The name is bytes of unbounded length, so it rides
|
||||
// the ONE client -> server byte carrier, SEG_STAGE, as a kHasBlob blob: Name.Size is
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// (DirectGLES.cpp documents that behaviour where it does the same comparison itself).
|
||||
//
|
||||
// Hand maintained alongside MGPipeTypes.h, MGPipeValueTypes.h, MGPipeHostSpan.h and
|
||||
// MG_Backend/BackendObject.h. Adding a member to one of these structs without adding it here
|
||||
// MG_Backend/BackendFunctions.h. Adding a member to one of these structs without adding it here
|
||||
// would make the comparator blind to it, so gen_pipe.py asserts - in both modes, hence in
|
||||
// pipe-gates - that every list below names exactly the direct data members of its struct
|
||||
// (P1 brief D8; a member named Pad<n> is padding and is not listed).
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
// G5b: the verb enum, the verb classes and their may-read field masks.
|
||||
//
|
||||
// GENERATED by scripts/gen_pipe.py from FillPoints.def, Coverage.def and MG_Backend/BackendObject.h - DO NOT EDIT.
|
||||
// GENERATED by scripts/gen_pipe.py from FillPoints.def, Coverage.def and MG_Backend/BackendFunctions.h - DO NOT EDIT.
|
||||
// Regenerate with `python3 scripts/gen_pipe.py`; CI runs it and diffs the result.
|
||||
// This file is included from MG_Pipe/MGPipe.h inside namespace MobileGL::MG_Pipe.
|
||||
|
||||
|
||||
@@ -436,7 +436,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
|
||||
Bool BackendObject_Remote::SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) {
|
||||
// NOT FORWARDED, and this is the one that must not be. The base implementation's last
|
||||
// act is GetBackendFunctions().Present() (BackendObject.cpp:396) - which is this
|
||||
// act is GetBackendFunctions().Present() - which is this
|
||||
// client's class-B Present EMITTER, the only route by which Present is reached at all
|
||||
// (it has zero MG_Impl call sites). Calling Server::ServerSwapEGLBuffers would present
|
||||
// on the server directly and put no record on the wire, which is the shape every gate
|
||||
|
||||
@@ -26,11 +26,13 @@
|
||||
|
||||
namespace MobileGL::MG_Remote::Client {
|
||||
|
||||
class BackendObject_Remote final : public MG_Backend::BackendObject {
|
||||
class BackendObject_Remote final : public MG_Backend::BackendObject, public MG_Backend::ClientHooks {
|
||||
public:
|
||||
BackendObject_Remote();
|
||||
~BackendObject_Remote() override;
|
||||
|
||||
MG_Backend::ClientHooks* AsClient() override { return this; }
|
||||
|
||||
// ---- the eight pure virtuals ------------------------------------------------------
|
||||
void Initialize() override;
|
||||
Bool InitCapabilities() override;
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// F1 (P7 wave 2): IT NEVER ANSWERS FROM A PLACEHOLDER ANY MORE. See RequireFirstSnapshot.
|
||||
Bool ServerConsumes(Uint64 subsystemBit) const;
|
||||
|
||||
// GLFunctionsTable::PrefersCpuXfbPrimitiveAccounting (BackendObject.h) does NOT
|
||||
// GLFunctionsTable::PrefersCpuXfbPrimitiveAccounting (BackendFunctions.h) does NOT
|
||||
// ride in MGPCaps::Dynamic - it is a member of the function table, which is precisely
|
||||
// the thing a split client never receives. Its only non-test client reader is
|
||||
// GL_Query.cpp:221, and under split it must be answered from kCapCpuXfbPrimitiveAccounting.
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
|
||||
#include <MG_Backend/BackendObject.h>
|
||||
#include <MG_Backend/BackendFunctions.h>
|
||||
// P5b: the clear discriminants below alias MGPipeTypes.h's (CONTRACT-P5B.md f1). The header
|
||||
// was already in this file's closure through BackendObject.h's neighbours; naming it makes
|
||||
// the dependency the aliases have explicit.
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#include <Includes.h>
|
||||
|
||||
#include <Config.h>
|
||||
#include <MG_Backend/BackendObject.h>
|
||||
#include <MG_Backend/BackendFunctions.h>
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include <MG_Pipe/MGPipeTypes.h>
|
||||
|
||||
@@ -509,45 +509,45 @@ namespace MobileGL::MG_Remote::Server {
|
||||
return holder.Release(op.ImageId);
|
||||
case MG_Pipe::kMGPSharedImagePresent: {
|
||||
SI::ImageRef image = holder.Get(op.ImageId);
|
||||
MG_Backend::BackendObject* backend = ServerLoopInstance().Backend();
|
||||
if (image == nullptr || backend == nullptr) return false;
|
||||
MG_Backend::ServerHooks* server = ServerLoopInstance().BackendHooks();
|
||||
if (image == nullptr || server == nullptr) return false;
|
||||
MG_Backend::SharedImageView view;
|
||||
view.Id = image->Id;
|
||||
view.Width = image->Width;
|
||||
view.Height = image->Height;
|
||||
view.Fourcc = image->Fourcc;
|
||||
view.NativeBuffer = image->Native;
|
||||
if (!backend->BlitDefaultFramebufferToSharedImage(view, DamageOfRecord(op.DamageCount, op.Damage)))
|
||||
if (!server->BlitDefaultFramebufferToSharedImage(view, DamageOfRecord(op.DamageCount, op.Damage)))
|
||||
return false;
|
||||
fill(*image);
|
||||
return true;
|
||||
}
|
||||
case MG_Pipe::kMGPSharedImageQueryBufferAge: {
|
||||
MG_Backend::BackendObject* backend = ServerLoopInstance().Backend();
|
||||
if (backend == nullptr) return false;
|
||||
MG_Backend::ServerHooks* server = ServerLoopInstance().BackendHooks();
|
||||
if (server == nullptr) return false;
|
||||
reply.BufferAge =
|
||||
backend->QueryCurrentBufferAge((op.Format & MG_Pipe::kMGPBufferAgeDamageRegionFollows) != 0);
|
||||
server->QueryCurrentBufferAge((op.Format & MG_Pipe::kMGPBufferAgeDamageRegionFollows) != 0);
|
||||
return true;
|
||||
}
|
||||
case MG_Pipe::kMGPSharedImageSetDamageRegion: {
|
||||
MG_Backend::BackendObject* backend = ServerLoopInstance().Backend();
|
||||
if (backend == nullptr) return false;
|
||||
return backend->SetCurrentDamageRegion(DamageOfRecord(op.DamageCount, op.Damage));
|
||||
MG_Backend::ServerHooks* server = ServerLoopInstance().BackendHooks();
|
||||
if (server == nullptr) return false;
|
||||
return server->SetCurrentDamageRegion(DamageOfRecord(op.DamageCount, op.Damage));
|
||||
}
|
||||
case MG_Pipe::kMGPSharedImageFlush: {
|
||||
// The answer is posted after the fence is published: the client commits the buffer to
|
||||
// its compositor only after this returns, so the compositor's session finds the write.
|
||||
MG_Backend::BackendObject* backend = ServerLoopInstance().Backend();
|
||||
if (backend == nullptr) return false;
|
||||
return backend->PublishSharedImageAccesses();
|
||||
MG_Backend::ServerHooks* server = ServerLoopInstance().BackendHooks();
|
||||
if (server == nullptr) return false;
|
||||
return server->PublishSharedImageAccesses();
|
||||
}
|
||||
case MG_Pipe::kMGPSharedImageNativeFence: {
|
||||
// A descriptor of the session's work, and an image writer's flush (MGPipeTypes.h). Like
|
||||
// an Allocate's, the descriptor goes out BEFORE the reply is posted.
|
||||
MG_Backend::BackendObject* backend = ServerLoopInstance().Backend();
|
||||
if (backend == nullptr) return false;
|
||||
MG_Backend::ServerHooks* server = ServerLoopInstance().BackendHooks();
|
||||
if (server == nullptr) return false;
|
||||
int fence = -1;
|
||||
if (!backend->ExportNativeFence(&fence)) return false;
|
||||
if (!server->ExportNativeFence(&fence)) return false;
|
||||
#if !defined(_WIN32)
|
||||
if (fence >= 0) {
|
||||
m_lastNativeFence.Reset(::dup(fence));
|
||||
@@ -626,8 +626,8 @@ namespace MobileGL::MG_Remote::Server {
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
(void)MG_Backend::DirectGLES::MakeNativeContextCurrentForBoundToken();
|
||||
// The backend half for one that keeps a single native surface binding (Magma).
|
||||
if (MG_Backend::BackendObject* backend = ServerLoopInstance().Backend()) {
|
||||
backend->OnClientContextBound(bind.ClientContextToken);
|
||||
if (MG_Backend::ServerHooks* server = ServerLoopInstance().BackendHooks()) {
|
||||
server->OnClientContextBound(bind.ClientContextToken);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -767,7 +767,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// Clear -> Clear, DrawVbo -> DrawArrays, ReadPixels -> ReadPixels, Blit -> BlitFramebuffer.
|
||||
//
|
||||
// PRESENT IS DELIBERATELY NOT ONE, although contract 7 puts it in class B: Present and
|
||||
// SetSwapInterval sit outside GLFunctionsTable (BackendObject.h) and read no frontend state,
|
||||
// SetSwapInterval sit outside GLFunctionsTable (BackendFunctions.h) and read no frontend state,
|
||||
// so FillPoints.def's MGP_FILL_VERB_LIST has no row for them. There is no MGPipeVerb::Present,
|
||||
// and stamping there would retire the previous verb's answers with nothing to put in their place.
|
||||
void PipeApplier::StampVerbBoundary(MG_Pipe::MGPWireOp op) {
|
||||
|
||||
@@ -393,6 +393,10 @@ namespace MobileGL::MG_Remote::Server {
|
||||
return m_backend.get();
|
||||
}
|
||||
|
||||
MG_Backend::ServerHooks* ServerLoop::BackendHooks() {
|
||||
return MG_Backend::ServerHooksOf(m_backend.get());
|
||||
}
|
||||
|
||||
MobileGLResult ServerLoop::InstallBackendForTesting(UniquePtr<MG_Backend::BackendObject> backend) {
|
||||
if (m_backend != nullptr || backend == nullptr || m_running.load(std::memory_order_acquire)) {
|
||||
return MOBILEGL_ERR_INVALID_ARGUMENT;
|
||||
@@ -644,9 +648,9 @@ namespace MobileGL::MG_Remote::Server {
|
||||
session.PeerSharedSegments() ? "shared segments" : "stream", policy.rule);
|
||||
|
||||
// A served session shares its process with other sessions: its backend's own native state
|
||||
// is bound to this thread before anything can apply a record (BackendObject.h). The inproc
|
||||
// is bound to this thread before anything can apply a record (BackendRoles.h). The inproc
|
||||
// one-process shape has no runtime and keeps the process-wide state.
|
||||
if (m_runtime != nullptr && m_backend != nullptr) m_backend->BindSessionStateToThisThread();
|
||||
if (m_runtime != nullptr && BackendHooks() != nullptr) BackendHooks()->BindSessionStateToThisThread();
|
||||
|
||||
// The decoder is built HERE, on this thread, because PipeWireDecoder is "not thread
|
||||
// safe: one decoder on the apply thread, by construction" and its constructor installs
|
||||
@@ -722,9 +726,9 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// The head is loaded BEFORE the poll, so every record below it was in the ring when the
|
||||
// poll ran and the drain need not poll for it again (DrainRing's `polledHead`).
|
||||
std::uint64_t polledHead = 0;
|
||||
if (m_backend != nullptr && !SessionLatched()) {
|
||||
if (BackendHooks() != nullptr && !SessionLatched()) {
|
||||
polledHead = signals.CmdHead != nullptr ? signals.CmdHead->load(std::memory_order_acquire) : 0;
|
||||
(void)m_backend->LatchIfGpuFaulted();
|
||||
(void)BackendHooks()->LatchIfGpuFaulted();
|
||||
}
|
||||
if (eventRingHasRoom()) DrainRing(polledHead);
|
||||
if (stopOrForfeit()) break;
|
||||
@@ -1141,7 +1145,8 @@ namespace MobileGL::MG_Remote::Server {
|
||||
record.kind == static_cast<std::uint16_t>(MG_Pipe::MGPWireOp::Present)) {
|
||||
if (unseen) polledHead = signals.CmdHead->load(std::memory_order_acquire);
|
||||
recordsSinceFaultCheck = 0;
|
||||
if (m_backend->LatchIfGpuFaulted()) return false;
|
||||
MG_Backend::ServerHooks* server = BackendHooks();
|
||||
if (server != nullptr && server->LatchIfGpuFaulted()) return false;
|
||||
}
|
||||
}
|
||||
if (SessionLatched()) return false;
|
||||
@@ -2185,7 +2190,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
}
|
||||
|
||||
void ServerLoop::PublishServerWindowResize() {
|
||||
if (!m_holdsWindowLease || m_backend == nullptr) return;
|
||||
if (!m_holdsWindowLease || BackendHooks() == nullptr) return;
|
||||
// THE DISPLAY'S EXTENT, READ NOW: the latest report wins over any a burst of them left queued.
|
||||
void* window = nullptr;
|
||||
Uint32 width = 0;
|
||||
@@ -2197,7 +2202,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
const Uint32 previousWidth = m_publishedWindowWidth;
|
||||
const Uint32 previousHeight = m_publishedWindowHeight;
|
||||
NoteServerWindowExtent(window, width, height);
|
||||
const Bool published = m_backend->ServerWindowResized(window, width, height);
|
||||
const Bool published = BackendHooks()->ServerWindowResized(window, width, height);
|
||||
if (published) m_serverWindowResizes.fetch_add(1, std::memory_order_acq_rel);
|
||||
MGLOG_I("MG_Remote server: server window %p resized %ux%u -> %ux%u; %s", window, previousWidth, previousHeight,
|
||||
width, height,
|
||||
@@ -2228,7 +2233,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// window surfaces, Magma the swapchain and VkSurfaceKHR - keeps its contexts, objects and
|
||||
// device, and the session waits for the display's next window with every record still applied.
|
||||
void* const window = m_leasedWindow;
|
||||
if (m_backend != nullptr && m_backend->SuspendServerWindow(window)) {
|
||||
if (BackendHooks() != nullptr && BackendHooks()->SuspendServerWindow(window)) {
|
||||
// N-3: the native binding moved off the window's surface.
|
||||
ForgetCurrentTuple();
|
||||
m_serverWindowSuspended.store(true, std::memory_order_release);
|
||||
@@ -2277,7 +2282,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
Bool ServerLoop::ResumeServerWindowOnLease(const ServerWindowLease& lease) {
|
||||
// Before the backend: its republished extent is the server window's (MGPipeServerOwnedWindow).
|
||||
NoteServerWindowExtent(lease.window, lease.width, lease.height);
|
||||
if (m_backend == nullptr || !m_backend->ResumeServerWindow(lease.window, lease.width, lease.height)) {
|
||||
if (BackendHooks() == nullptr || !BackendHooks()->ResumeServerWindow(lease.window, lease.width, lease.height)) {
|
||||
LatchServerWindowLost("the suspended on-screen session's surfaces could not be rebuilt on the new server "
|
||||
"window");
|
||||
return false;
|
||||
|
||||
@@ -111,6 +111,9 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// lookups - take the format cache as a parameter instead. That is six functions across
|
||||
// two files, and it is why no thread-keyed shim is needed for MOBILEGL_BUILD_DISAGGREGATED_INPROC.
|
||||
MG_Backend::BackendObject* Backend();
|
||||
// Its server hooks (BackendRoles.h); null with no backend. Asked at each use, never kept: a
|
||||
// test may swap the backend under a running apply thread.
|
||||
MG_Backend::ServerHooks* BackendHooks();
|
||||
|
||||
// Runs one blocking control request on the apply thread and waits for it; every Server*
|
||||
// EGL forwarder funnels through here. Blocking rather than async, because each caller
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#include "Init.h"
|
||||
#include <Config.h>
|
||||
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include <MG_Backend/BackendFunctions.h>
|
||||
#include <MG_Impl/GLImpl/Getter/GL_Getter.h>
|
||||
#include <MG_Impl/GLImpl/Query/GL_Query.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
#include "Includes.h"
|
||||
#include "Init.h"
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include <MG_Backend/BackendFunctions.h>
|
||||
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
|
||||
#include <MG_Impl/GLImpl/Getter/GL_Getter.h>
|
||||
#include <MG_Impl/GLImpl/Texture/GL_Texture.h>
|
||||
|
||||
@@ -314,9 +314,9 @@ namespace {
|
||||
return Note(r, "the owner's context did not go current");
|
||||
r.initialized = 1;
|
||||
|
||||
auto* backend = MG_Backend::pActiveBackendObject.get();
|
||||
auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get());
|
||||
MG_Backend::SharedImageExport image;
|
||||
if (backend == nullptr || !backend->AllocateSharedImage(kImageWidth, kImageHeight, kXrgb8888, &image) ||
|
||||
if (client == nullptr || !client->AllocateSharedImage(kImageWidth, kImageHeight, kXrgb8888, &image) ||
|
||||
image.Fd < 0)
|
||||
return Note(r, "no shared image was allocated");
|
||||
r.allocated = 1;
|
||||
@@ -339,12 +339,12 @@ namespace {
|
||||
r.pixelAfter = ClearAndRead(1.0f, 0.0f, 0.0f);
|
||||
Uint64 again = 0;
|
||||
r.stillIdentified =
|
||||
backend->ImportSharedImage(image.Fd, image.Width, image.Height, image.Fourcc, &again) && again == image.Id
|
||||
client->ImportSharedImage(image.Fd, image.Width, image.Height, image.Fourcc, &again) && again == image.Id
|
||||
? 1
|
||||
: 0;
|
||||
if (again != 0) (void)backend->ReleaseSharedImage(again);
|
||||
if (again != 0) (void)client->ReleaseSharedImage(again);
|
||||
::close(image.Fd);
|
||||
(void)backend->ReleaseSharedImage(image.Id);
|
||||
(void)client->ReleaseSharedImage(image.Id);
|
||||
(void)EGL::MakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
(void)EGL::DestroyContext(dpy, context);
|
||||
(void)EGL::DestroySurface(dpy, surface);
|
||||
|
||||
@@ -163,9 +163,9 @@ namespace {
|
||||
|
||||
// A window pixmap: a shared image (what gbm_bo_create gives glamor), bound through
|
||||
// EGL_LINUX_DMA_BUF_EXT the way xwl_glamor_gbm_create_pixmap_for_bo does.
|
||||
auto* backend = MG_Backend::pActiveBackendObject.get();
|
||||
auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get());
|
||||
MG_Backend::SharedImageExport image;
|
||||
if (backend != nullptr && backend->AllocateSharedImage(64, 32, kXrgb8888, &image) && image.Fd >= 0) {
|
||||
if (client != nullptr && client->AllocateSharedImage(64, 32, kXrgb8888, &image) && image.Fd >= 0) {
|
||||
const EGLAttrib attribs[] = {EGL_WIDTH,
|
||||
64,
|
||||
EGL_HEIGHT,
|
||||
@@ -183,16 +183,16 @@ namespace {
|
||||
r.imageCreated = eglImage != EGL_NO_IMAGE ? 1 : 0;
|
||||
if (eglImage != EGL_NO_IMAGE) (void)EGL::DestroyImage(dpy, eglImage);
|
||||
::close(image.Fd);
|
||||
(void)backend->ReleaseSharedImage(image.Id);
|
||||
(void)client->ReleaseSharedImage(image.Id);
|
||||
}
|
||||
|
||||
// glamor's block handler: glFlush, which in an X server is where its renders become visible
|
||||
// to the compositor. The verb must be answered (an unknown verb is refused).
|
||||
using ClearFn = void (*)(unsigned);
|
||||
if (const auto clear = reinterpret_cast<ClearFn>(EGL::GetProcAddress("glClear"))) clear(0x4000);
|
||||
r.flushAnswered = backend != nullptr && backend->FlushSharedImageAccesses() ? 1 : 0;
|
||||
r.flushAnswered = client != nullptr && client->FlushSharedImageAccesses() ? 1 : 0;
|
||||
// Nothing reached the stream since: no round trip, still a success.
|
||||
r.flushAgainAnswered = backend != nullptr && backend->FlushSharedImageAccesses() ? 1 : 0;
|
||||
r.flushAgainAnswered = client != nullptr && client->FlushSharedImageAccesses() ? 1 : 0;
|
||||
|
||||
(void)EGL::MakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
(void)EGL::DestroyContext(dpy, ctx);
|
||||
@@ -272,7 +272,7 @@ namespace {
|
||||
|
||||
// A GBM scanout buffer (a shared image), bound as a texture and rendered into through a
|
||||
// framebuffer - and then no glFlush, as Chrome does when it has native fences.
|
||||
auto* backend = MG_Backend::pActiveBackendObject.get();
|
||||
auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get());
|
||||
MG_Backend::SharedImageExport image;
|
||||
EGLImage eglImage = EGL_NO_IMAGE;
|
||||
using GenFn = void (*)(int, unsigned*);
|
||||
@@ -291,7 +291,7 @@ namespace {
|
||||
const auto checkStatus = reinterpret_cast<StatusFn>(EGL::GetProcAddress("glCheckFramebufferStatus"));
|
||||
const auto clearColor = reinterpret_cast<ClearColorFn>(EGL::GetProcAddress("glClearColor"));
|
||||
const auto clear = reinterpret_cast<ClearFn>(EGL::GetProcAddress("glClear"));
|
||||
if (backend != nullptr && backend->AllocateSharedImage(64, 32, kArgb8888, &image) && image.Fd >= 0) {
|
||||
if (client != nullptr && client->AllocateSharedImage(64, 32, kArgb8888, &image) && image.Fd >= 0) {
|
||||
const EGLAttrib attribs[] = {EGL_WIDTH,
|
||||
64,
|
||||
EGL_HEIGHT,
|
||||
@@ -354,7 +354,7 @@ namespace {
|
||||
|
||||
if (eglImage != EGL_NO_IMAGE) (void)EGL::DestroyImage(dpy, eglImage);
|
||||
if (image.Fd >= 0) ::close(image.Fd);
|
||||
if (backend != nullptr && image.Id != 0) (void)backend->ReleaseSharedImage(image.Id);
|
||||
if (client != nullptr && image.Id != 0) (void)client->ReleaseSharedImage(image.Id);
|
||||
(void)EGL::MakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
(void)EGL::DestroyContext(dpy, ctx);
|
||||
(void)EGL::Terminate(dpy);
|
||||
@@ -399,12 +399,12 @@ namespace {
|
||||
// The GL extension list is where a split client asks the server whether it holds YUV images.
|
||||
r.externalImage = HasGlExtension("GL_OES_EGL_image_external") ? 1 : 0;
|
||||
// Asked anyway (a C-ABI or GLX caller does not look at the extensions first): refused.
|
||||
auto* backend = MG_Backend::pActiveBackendObject.get();
|
||||
auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get());
|
||||
MG_Backend::SharedImageExport image;
|
||||
if (backend != nullptr && backend->AllocateSharedImage(64, 32, kXrgb8888, &image)) {
|
||||
if (client != nullptr && client->AllocateSharedImage(64, 32, kXrgb8888, &image)) {
|
||||
r.allocated = 1;
|
||||
if (image.Fd >= 0) ::close(image.Fd);
|
||||
(void)backend->ReleaseSharedImage(image.Id);
|
||||
(void)client->ReleaseSharedImage(image.Id);
|
||||
}
|
||||
(void)EGL::MakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
(void)EGL::DestroyContext(dpy, ctx);
|
||||
|
||||
@@ -131,8 +131,9 @@ struct ErrorPeer : Codec::ReplySink {
|
||||
}
|
||||
};
|
||||
|
||||
struct CapsPeer : MG_Backend::BackendObject {
|
||||
struct CapsPeer : MG_Backend::BackendObject, MG_Backend::ServerHooks {
|
||||
MG_Backend::DynamicBackendParameters dynamic{};
|
||||
ServerHooks* AsServer() override { return this; }
|
||||
MG_Backend::GlobalBackendFunctionsTable table{};
|
||||
RendererInfo renderer{};
|
||||
void Initialize() override {}
|
||||
|
||||
@@ -3634,7 +3634,7 @@ TEST(ServerLoopLatchTest, ALostServerWindowIsReleasedMidBatchWhileTheClientStrea
|
||||
}
|
||||
|
||||
// =====================================================================================
|
||||
// A SESSION WHOSE OWN GPU WORK HUNG THE GPU APPLIES NOTHING MORE (BackendObject::LatchIfGpuFaulted)
|
||||
// A SESSION WHOSE OWN GPU WORK HUNG THE GPU APPLIES NOTHING MORE (ServerHooks::LatchIfGpuFaulted)
|
||||
// =====================================================================================
|
||||
|
||||
namespace {
|
||||
@@ -3642,9 +3642,10 @@ namespace {
|
||||
// watch's thread (Magma) - while the session's next frame is already queued. The double counts
|
||||
// the frames that reach it and, once `faulted` is set, latches its session from the apply thread
|
||||
// as both real backends do.
|
||||
class GpuFaultBackendDouble final : public MG_Backend::BackendObject {
|
||||
class GpuFaultBackendDouble final : public MG_Backend::BackendObject, public MG_Backend::ServerHooks {
|
||||
public:
|
||||
GpuFaultBackendDouble() { m_functions.Present = &Present; }
|
||||
ServerHooks* AsServer() override { return this; }
|
||||
Bool LatchIfGpuFaulted() override {
|
||||
asked.fetch_add(1, std::memory_order_acq_rel);
|
||||
if (!faulted.load(std::memory_order_acquire)) return false;
|
||||
@@ -3758,10 +3759,10 @@ namespace {
|
||||
// window - presents from then on reach no window - and by taking the new one. Its Present counts
|
||||
// the frames and the ones that came while no window was held. Statics, because the loop owns
|
||||
// (and destroys) the object and each case runs in its own death-test child.
|
||||
class WindowBackendDouble final : public MG_Backend::BackendObject {
|
||||
class WindowBackendDouble final : public MG_Backend::BackendObject, public MG_Backend::ServerHooks {
|
||||
public:
|
||||
explicit WindowBackendDouble(Bool canSuspend) : m_canSuspend(canSuspend) { m_functions.Present = &Present; }
|
||||
|
||||
ServerHooks* AsServer() override { return this; }
|
||||
void Initialize() override {}
|
||||
Bool InitCapabilities() override { return true; }
|
||||
Bool InitWindowSurface() override { return true; }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"allcaps_comment_lines": 2253,
|
||||
"line_citations": 159,
|
||||
"line_citations": 156,
|
||||
"long_comment_excess_lines": 8895,
|
||||
"non_english_comment_lines": 20,
|
||||
"planning_code_comment_lines": 3188
|
||||
|
||||
+3
-3
@@ -45,7 +45,7 @@ REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
PIPE_DIR = os.path.join(REPO_ROOT, "MobileGL", "MG_Pipe")
|
||||
GENERATED_DIR = os.path.join(PIPE_DIR, "generated")
|
||||
INVENTORY = os.path.join(REPO_ROOT, "scripts", "data", "backend_read_inventory.md")
|
||||
FUNCTION_TABLE_HEADER = os.path.join(REPO_ROOT, "MobileGL", "MG_Backend", "BackendObject.h")
|
||||
FUNCTION_TABLE_HEADER = os.path.join(REPO_ROOT, "MobileGL", "MG_Backend", "BackendFunctions.h")
|
||||
|
||||
GENERATED_BANNER = """// MobileGL - MobileGL/MG_Pipe/generated/{name}
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
@@ -227,7 +227,7 @@ MEMCMP_FALLBACK_TYPES = set()
|
||||
|
||||
# Where the structs named in PipeFields.def are declared: the payload header, the value
|
||||
# header, the host-span header, the backend capability header (DynamicBackendParameters, the
|
||||
# caps block) and the backend object header.
|
||||
# caps block) and the backend function table header.
|
||||
FIELD_LIST_STRUCT_HEADERS = [
|
||||
os.path.join(PIPE_DIR, "MGPipeTypes.h"),
|
||||
os.path.join(PIPE_DIR, "MGPipeValueTypes.h"),
|
||||
@@ -1344,7 +1344,7 @@ def gen_fill_points(accessors, sticky, verbs, classes, fields):
|
||||
words = max(2, (len(accessors) + 63) // 64)
|
||||
sticky_names = [name for name, _ in sticky]
|
||||
out = [banner("PipeFillPoints.inc", "G5b: the verb enum, the verb classes and their may-read field masks.",
|
||||
"FillPoints.def, Coverage.def and MG_Backend/BackendObject.h")]
|
||||
"FillPoints.def, Coverage.def and MG_Backend/BackendFunctions.h")]
|
||||
out.append("""// One verb per function-pointer member of MG_Backend::GLFunctionsTable, in declaration
|
||||
// order, so the enum IS the table's member list. MG_Impl spells MGP_FILL(Verb) before every
|
||||
// call through the table; MGPipeFillForVerb fills exactly the fields of the verb's class
|
||||
|
||||
Reference in New Issue
Block a user