mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-10 21:28:32 +09:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10d0441040 | ||
|
|
ad03e059e0 | ||
|
|
440e569c98 | ||
|
|
cb62431299 | ||
|
|
06ce55dac3 | ||
|
|
7400f46955 | ||
|
|
1679bf9a1e | ||
|
|
8673e89b13 |
@@ -210,6 +210,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (options & PixelFormatNormalizeOptionBit::NoDepthComponent32) {
|
||||
reasons.push_back("GL_DEPTH_COMPONENT32 native probe failed on OpenGL ES");
|
||||
}
|
||||
if (options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget) {
|
||||
reasons.push_back("no three-channel multisample storage format on OpenGL ES");
|
||||
}
|
||||
if (options & PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget) {
|
||||
reasons.push_back("EXT_render_snorm not supported");
|
||||
}
|
||||
|
||||
String reason;
|
||||
for (SizeT i = 0; i < reasons.size(); ++i) {
|
||||
@@ -556,20 +562,50 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
const GLESProbeFormatInfo nativeInfo = BuildNativeProbeFormatInfo(requestedInternalFormat);
|
||||
GLESProbeFormatInfo fallbackInfo;
|
||||
const Bool hasForcedFallback =
|
||||
BuildFallbackProbeFormatInfo(requestedInternalFormat, forcedOptions, true, fallbackInfo);
|
||||
if (!hasForcedFallback) {
|
||||
BuildFallbackProbeFormatInfo(requestedInternalFormat, driverOptions, false, fallbackInfo);
|
||||
GLESProbeFormatInfo outerFallbackInfo;
|
||||
const Bool outerHasForcedFallback =
|
||||
BuildFallbackProbeFormatInfo(requestedInternalFormat, forcedOptions, true, outerFallbackInfo);
|
||||
if (!outerHasForcedFallback) {
|
||||
BuildFallbackProbeFormatInfo(requestedInternalFormat, driverOptions, false, outerFallbackInfo);
|
||||
}
|
||||
|
||||
for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTextureTargetCount; ++targetIndex) {
|
||||
const auto target = static_cast<TextureTarget>(targetIndex);
|
||||
// A multisample texture can only ever be rendered into, so its storage format
|
||||
// has to stay colour-renderable; the ordinary fallback for a three-channel
|
||||
// format is a three-channel one, which ES accepts as a texture but rejects as
|
||||
// multisample storage. Recompute the fallback per target so those formats get
|
||||
// widened here and nowhere else.
|
||||
Flags<PixelFormatNormalizeOptionBit> targetOptions;
|
||||
if (IsGLESProbeMultisampleTarget(target)) {
|
||||
targetOptions |= PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget;
|
||||
if (!capabilities.SupportsRenderSnorm || !capabilities.SupportsNorm16Texture) {
|
||||
targetOptions |= PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget;
|
||||
}
|
||||
}
|
||||
GLESProbeFormatInfo fallbackInfo = outerFallbackInfo;
|
||||
Bool hasForcedFallback = outerHasForcedFallback;
|
||||
if (targetOptions) {
|
||||
hasForcedFallback = BuildFallbackProbeFormatInfo(
|
||||
requestedInternalFormat, forcedOptions | targetOptions, true, fallbackInfo);
|
||||
if (!hasForcedFallback) {
|
||||
BuildFallbackProbeFormatInfo(requestedInternalFormat, driverOptions | targetOptions,
|
||||
false, fallbackInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// 1D, 1D-array and rectangle textures live on an ES target (see
|
||||
// TextureImpl::MapToBackendTextureTarget), so they have to be probed there too -
|
||||
// probing the desktop-only target itself always failed, which left those slots
|
||||
// of the cache empty and stopped any fallback format from being selected for
|
||||
// them (a GL_DEPTH_COMPONENT32 1D texture then got no storage at all).
|
||||
const TextureTarget probeTarget = TextureImpl::MapToBackendTextureTarget(target);
|
||||
|
||||
Bool shouldProbeFallback = hasForcedFallback;
|
||||
if (!hasForcedFallback) {
|
||||
Bool nativeRenderable = false;
|
||||
const Bool nativeCreated =
|
||||
ProbeTexture(gl, target, nativeInfo.InternalFormat, nativeInfo.ImageFormat,
|
||||
ProbeTexture(gl, probeTarget, nativeInfo.InternalFormat, nativeInfo.ImageFormat,
|
||||
nativeInfo.ImageType, logicalFormat, &nativeRenderable);
|
||||
if (nativeCreated) {
|
||||
AddFullFormatCaps(cache, targetIndex, formatIndex,
|
||||
@@ -584,7 +620,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (shouldProbeFallback && fallbackInfo.InternalFormat != GL_UNKNOWN_MGL) {
|
||||
Bool fallbackRenderable = false;
|
||||
const Bool fallbackCreated =
|
||||
ProbeTexture(gl, target, fallbackInfo.InternalFormat, fallbackInfo.ImageFormat,
|
||||
ProbeTexture(gl, probeTarget, fallbackInfo.InternalFormat, fallbackInfo.ImageFormat,
|
||||
fallbackInfo.ImageType, logicalFormat, &fallbackRenderable);
|
||||
if (fallbackCreated) {
|
||||
if (AddCaveatFormatCaps(cache, targetIndex, formatIndex,
|
||||
@@ -600,8 +636,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
const SizeT renderbufferTargetIndex = GetRenderbufferFormatCapabilityTargetIndex();
|
||||
Bool shouldProbeFallbackRenderbuffer = hasForcedFallback;
|
||||
if (!hasForcedFallback) {
|
||||
Bool shouldProbeFallbackRenderbuffer = outerHasForcedFallback;
|
||||
if (!outerHasForcedFallback) {
|
||||
const Bool nativeRenderbufferComplete =
|
||||
ProbeRenderbuffer(gl, nativeInfo.InternalFormat, logicalFormat, false, 1);
|
||||
if (nativeRenderbufferComplete) {
|
||||
@@ -615,16 +651,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
shouldProbeFallbackRenderbuffer = true;
|
||||
}
|
||||
}
|
||||
if (shouldProbeFallbackRenderbuffer && fallbackInfo.InternalFormat != GL_UNKNOWN_MGL &&
|
||||
ProbeRenderbuffer(gl, fallbackInfo.InternalFormat, logicalFormat, false, 1)) {
|
||||
if (shouldProbeFallbackRenderbuffer && outerFallbackInfo.InternalFormat != GL_UNKNOWN_MGL &&
|
||||
ProbeRenderbuffer(gl, outerFallbackInfo.InternalFormat, logicalFormat, false, 1)) {
|
||||
if (AddCaveatFormatCaps(cache, renderbufferTargetIndex, formatIndex,
|
||||
GetRenderbufferFeatureCaps(logicalFormat))) {
|
||||
LogGLESFormatCaveat(logicalFormat, renderbufferTargetIndex, fallbackInfo);
|
||||
LogGLESFormatCaveat(logicalFormat, renderbufferTargetIndex, outerFallbackInfo);
|
||||
}
|
||||
const Int maxSamples =
|
||||
GetGLESFormatMaxSamples(capabilities, logicalFormat, fallbackInfo.ImageFormat);
|
||||
GetGLESFormatMaxSamples(capabilities, logicalFormat, outerFallbackInfo.ImageFormat);
|
||||
cache.SampleCounts[renderbufferTargetIndex][formatIndex] =
|
||||
ProbeRenderbufferSampleCounts(gl, fallbackInfo.InternalFormat, logicalFormat, maxSamples);
|
||||
ProbeRenderbufferSampleCounts(gl, outerFallbackInfo.InternalFormat, logicalFormat, maxSamples);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -738,6 +738,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
static Bool g_hasSyncedRenderState = false;
|
||||
static RenderStateParameters g_syncedRenderStateParameters;
|
||||
static IntVec4 g_syncedBackendViewport = IntVec4(-1, -1, -1, -1);
|
||||
// GLES starts with sRGB framebuffer encoding on, so the first sync always has to push the
|
||||
// frontend's (desktop-GL default) disabled state down.
|
||||
static Bool g_syncedSrgbFramebufferWrites = true;
|
||||
void SyncRenderState() {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
@@ -784,6 +787,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
#undef SYNC_CAPABILITY
|
||||
|
||||
{ // sRGB framebuffer writes. GLES core always encodes a write into an sRGB attachment,
|
||||
// while GL_FRAMEBUFFER_SRGB is disabled by default in desktop GL and the frontend
|
||||
// never turns it on, so the driver has to be told to write raw. Without this a render
|
||||
// into an sRGB colour buffer comes back encoded once too often (the shader's own
|
||||
// decode on the next fetch then leaves the value one conversion short).
|
||||
const Bool srgbWrites = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::FramebufferSrgb);
|
||||
if (g_GLESCapabilities.SupportsSrgbWriteControl && srgbWrites != g_syncedSrgbFramebufferWrites) {
|
||||
srgbWrites ? g_GLESFuncs.glEnable(GL_FRAMEBUFFER_SRGB)
|
||||
: g_GLESFuncs.glDisable(GL_FRAMEBUFFER_SRGB);
|
||||
g_syncedSrgbFramebufferWrites = srgbWrites;
|
||||
}
|
||||
}
|
||||
|
||||
{ // Primitive restart. GLES core has only GL_PRIMITIVE_RESTART_FIXED_INDEX (fixed all-ones
|
||||
// value); both the fixed cap and the (fixed-valued) arbitrary GL_PRIMITIVE_RESTART map to
|
||||
// it. An arbitrary non-fixed restart index is rejected at draw time (see DrawElements).
|
||||
@@ -2366,15 +2382,467 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return resolved;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// Single-sample -> multisample blit ("replicate")
|
||||
//
|
||||
// Desktop GL replicates the source sample into every destination sample when the read
|
||||
// framebuffer is single-sampled and the draw framebuffer is not. ES forbids the whole
|
||||
// call ("INVALID_OPERATION if SAMPLE_BUFFERS for the draw framebuffer is greater than
|
||||
// zero"), so the blit silently did nothing - KHR-GL3x.packed_depth_stencil.blit's
|
||||
// second loop then read a destination that still held its clear values.
|
||||
//
|
||||
// Emulated by drawing a full-screen triangle into the multisample framebuffer: every
|
||||
// pixel is fully covered, so every sample of it receives the same value, which is
|
||||
// exactly what the replicate rule asks for. Depth comes from gl_FragDepth; stencil has
|
||||
// no shader output on ES, so it is written one bit plane at a time with REPLACE and a
|
||||
// discard for the pixels whose source bit is clear.
|
||||
namespace ReplicateBlitImpl {
|
||||
static Uint s_contextGeneration = ~0u;
|
||||
static GLuint s_framebuffer = 0;
|
||||
static GLuint s_texture = 0;
|
||||
static GLenum s_textureFormat = 0;
|
||||
static GLsizei s_textureWidth = 0;
|
||||
static GLsizei s_textureHeight = 0;
|
||||
static GLuint s_vertexArray = 0;
|
||||
static GLuint s_depthProgram = 0;
|
||||
static GLuint s_stencilProgram = 0;
|
||||
static GLint s_depthUvTransform = -1;
|
||||
static GLint s_stencilUvTransform = -1;
|
||||
static GLint s_stencilBit = -1;
|
||||
static Bool s_programsFailed = false;
|
||||
|
||||
static const char* const kVertexSource =
|
||||
"#version 300 es\n"
|
||||
"uniform vec4 uUvTransform;\n"
|
||||
"out vec2 vUv;\n"
|
||||
"void main() {\n"
|
||||
" vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));\n"
|
||||
" gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);\n"
|
||||
" vUv = p * uUvTransform.xy + uUvTransform.zw;\n"
|
||||
"}\n";
|
||||
|
||||
static const char* const kDepthFragmentSource =
|
||||
"#version 300 es\n"
|
||||
"precision highp float;\n"
|
||||
"precision highp sampler2D;\n"
|
||||
"uniform sampler2D uSource;\n"
|
||||
"in vec2 vUv;\n"
|
||||
"void main() {\n"
|
||||
" gl_FragDepth = texture(uSource, vUv).r;\n"
|
||||
"}\n";
|
||||
|
||||
static const char* const kStencilFragmentSource =
|
||||
"#version 300 es\n"
|
||||
"precision highp float;\n"
|
||||
"precision highp usampler2D;\n"
|
||||
"uniform usampler2D uSource;\n"
|
||||
"uniform uint uBit;\n"
|
||||
"in vec2 vUv;\n"
|
||||
"void main() {\n"
|
||||
" if ((texture(uSource, vUv).r & uBit) == 0u) discard;\n"
|
||||
"}\n";
|
||||
|
||||
static GLuint BuildProgram(const char* fragmentSource) {
|
||||
const GLuint vertexShader = g_GLESFuncs.glCreateShader(GL_VERTEX_SHADER);
|
||||
const GLuint fragmentShader = g_GLESFuncs.glCreateShader(GL_FRAGMENT_SHADER);
|
||||
if (vertexShader == 0 || fragmentShader == 0) {
|
||||
return 0;
|
||||
}
|
||||
g_GLESFuncs.glShaderSource(vertexShader, 1, &kVertexSource, nullptr);
|
||||
g_GLESFuncs.glCompileShader(vertexShader);
|
||||
g_GLESFuncs.glShaderSource(fragmentShader, 1, &fragmentSource, nullptr);
|
||||
g_GLESFuncs.glCompileShader(fragmentShader);
|
||||
|
||||
const GLuint program = g_GLESFuncs.glCreateProgram();
|
||||
GLint linked = GL_FALSE;
|
||||
if (program != 0) {
|
||||
g_GLESFuncs.glAttachShader(program, vertexShader);
|
||||
g_GLESFuncs.glAttachShader(program, fragmentShader);
|
||||
g_GLESFuncs.glLinkProgram(program);
|
||||
g_GLESFuncs.glGetProgramiv(program, GL_LINK_STATUS, &linked);
|
||||
}
|
||||
g_GLESFuncs.glDeleteShader(vertexShader);
|
||||
g_GLESFuncs.glDeleteShader(fragmentShader);
|
||||
if (linked != GL_TRUE) {
|
||||
if (program != 0) g_GLESFuncs.glDeleteProgram(program);
|
||||
return 0;
|
||||
}
|
||||
return program;
|
||||
}
|
||||
|
||||
static Bool EnsureResources() {
|
||||
if (s_contextGeneration != TextureImpl::g_textureContextGeneration) {
|
||||
// The ids belonged to a dead context; the context reclaimed them with it.
|
||||
s_framebuffer = 0;
|
||||
s_texture = 0;
|
||||
s_textureFormat = 0;
|
||||
s_textureWidth = 0;
|
||||
s_textureHeight = 0;
|
||||
s_vertexArray = 0;
|
||||
s_depthProgram = 0;
|
||||
s_stencilProgram = 0;
|
||||
s_programsFailed = false;
|
||||
s_contextGeneration = TextureImpl::g_textureContextGeneration;
|
||||
}
|
||||
if (s_programsFailed) {
|
||||
return false;
|
||||
}
|
||||
if (s_depthProgram == 0) {
|
||||
s_depthProgram = BuildProgram(kDepthFragmentSource);
|
||||
s_stencilProgram = BuildProgram(kStencilFragmentSource);
|
||||
if (s_depthProgram == 0 || s_stencilProgram == 0) {
|
||||
s_programsFailed = true;
|
||||
MGLOG_E("BlitFramebuffer: could not build the multisample replicate programs");
|
||||
return false;
|
||||
}
|
||||
s_depthUvTransform = g_GLESFuncs.glGetUniformLocation(s_depthProgram, "uUvTransform");
|
||||
s_stencilUvTransform = g_GLESFuncs.glGetUniformLocation(s_stencilProgram, "uUvTransform");
|
||||
s_stencilBit = g_GLESFuncs.glGetUniformLocation(s_stencilProgram, "uBit");
|
||||
}
|
||||
if (s_framebuffer == 0) {
|
||||
g_GLESFuncs.glGenFramebuffers(1, &s_framebuffer);
|
||||
if (s_framebuffer == 0) return false;
|
||||
}
|
||||
if (s_vertexArray == 0) {
|
||||
g_GLESFuncs.glGenVertexArrays(1, &s_vertexArray);
|
||||
if (s_vertexArray == 0) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Sized internal format of the read framebuffer's depth (or, failing that, stencil)
|
||||
// attachment. The scratch copy has to use the very same one: ES rejects a
|
||||
// depth/stencil blit between differing formats even when both sides are single-sampled.
|
||||
static GLenum QueryReadDepthStencilFormat(GLenum* outAttachment) {
|
||||
const GLenum attachments[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
|
||||
for (const GLenum attachment : attachments) {
|
||||
GLint objectType = 0;
|
||||
GLint objectName = 0;
|
||||
g_GLESFuncs.glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER, attachment,
|
||||
GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &objectType);
|
||||
g_GLESFuncs.glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER, attachment,
|
||||
GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &objectName);
|
||||
if (objectName == 0) {
|
||||
continue;
|
||||
}
|
||||
GLint internalFormat = 0;
|
||||
if (objectType == GL_RENDERBUFFER) {
|
||||
GLint previous = 0;
|
||||
g_GLESFuncs.glGetIntegerv(GL_RENDERBUFFER_BINDING, &previous);
|
||||
g_GLESFuncs.glBindRenderbuffer(GL_RENDERBUFFER, static_cast<GLuint>(objectName));
|
||||
g_GLESFuncs.glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_INTERNAL_FORMAT,
|
||||
&internalFormat);
|
||||
g_GLESFuncs.glBindRenderbuffer(GL_RENDERBUFFER, static_cast<GLuint>(previous));
|
||||
} else if (objectType == GL_TEXTURE) {
|
||||
GLint previous = 0;
|
||||
g_GLESFuncs.glGetIntegerv(GL_TEXTURE_BINDING_2D, &previous);
|
||||
g_GLESFuncs.glBindTexture(GL_TEXTURE_2D, static_cast<GLuint>(objectName));
|
||||
g_GLESFuncs.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT,
|
||||
&internalFormat);
|
||||
g_GLESFuncs.glBindTexture(GL_TEXTURE_2D, static_cast<GLuint>(previous));
|
||||
}
|
||||
if (internalFormat != 0) {
|
||||
if (outAttachment) *outAttachment = attachment;
|
||||
return static_cast<GLenum>(internalFormat);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Bool FormatHasDepth(GLenum internalFormat) {
|
||||
return internalFormat == GL_DEPTH_COMPONENT16 || internalFormat == GL_DEPTH_COMPONENT24 ||
|
||||
internalFormat == GL_DEPTH_COMPONENT32F || internalFormat == GL_DEPTH24_STENCIL8 ||
|
||||
internalFormat == GL_DEPTH32F_STENCIL8;
|
||||
}
|
||||
|
||||
static Bool FormatHasStencil(GLenum internalFormat) {
|
||||
return internalFormat == GL_DEPTH24_STENCIL8 || internalFormat == GL_DEPTH32F_STENCIL8 ||
|
||||
internalFormat == GL_STENCIL_INDEX8;
|
||||
}
|
||||
|
||||
static GLenum ScratchAttachmentFor(GLenum internalFormat) {
|
||||
if (FormatHasDepth(internalFormat) && FormatHasStencil(internalFormat)) {
|
||||
return GL_DEPTH_STENCIL_ATTACHMENT;
|
||||
}
|
||||
return FormatHasDepth(internalFormat) ? GL_DEPTH_ATTACHMENT : GL_STENCIL_ATTACHMENT;
|
||||
}
|
||||
} // namespace ReplicateBlitImpl
|
||||
|
||||
// Returns true when the request was serviced (or is not this fallback's business).
|
||||
static Bool ReplicateBlitIntoMultisampleDraw(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0,
|
||||
GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask) {
|
||||
using namespace ReplicateBlitImpl;
|
||||
if ((mask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) == 0) {
|
||||
return false;
|
||||
}
|
||||
if (!EnsureResources()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const GLsizei srcWidth = static_cast<GLsizei>(std::abs(srcX1 - srcX0));
|
||||
const GLsizei srcHeight = static_cast<GLsizei>(std::abs(srcY1 - srcY0));
|
||||
const GLsizei dstWidth = static_cast<GLsizei>(std::abs(dstX1 - dstX0));
|
||||
const GLsizei dstHeight = static_cast<GLsizei>(std::abs(dstY1 - dstY0));
|
||||
if (srcWidth <= 0 || srcHeight <= 0 || dstWidth <= 0 || dstHeight <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GLenum readAttachment = GL_DEPTH_ATTACHMENT;
|
||||
const GLenum sourceFormat = QueryReadDepthStencilFormat(&readAttachment);
|
||||
if (sourceFormat == 0) {
|
||||
return false;
|
||||
}
|
||||
const Bool wantDepth = (mask & GL_DEPTH_BUFFER_BIT) != 0 && FormatHasDepth(sourceFormat);
|
||||
const Bool wantStencil = (mask & GL_STENCIL_BUFFER_BIT) != 0 && FormatHasStencil(sourceFormat);
|
||||
if (!wantDepth && !wantStencil) {
|
||||
return false;
|
||||
}
|
||||
// Sampling the stencil half of a packed texture goes through
|
||||
// GL_DEPTH_STENCIL_TEXTURE_MODE, which is ES 3.1 state; on an older driver the pname
|
||||
// would just raise GL_INVALID_ENUM and the shader would read depth bits as stencil.
|
||||
const Bool supportsStencilTextureMode = g_GLESCapabilities.GLESVersion.Major > 3 ||
|
||||
(g_GLESCapabilities.GLESVersion.Major == 3 &&
|
||||
g_GLESCapabilities.GLESVersion.Minor >= 1);
|
||||
if (wantStencil && !supportsStencilTextureMode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GLint previousDraw = 0;
|
||||
GLint previousRead = 0;
|
||||
g_GLESFuncs.glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previousDraw);
|
||||
g_GLESFuncs.glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &previousRead);
|
||||
GLint previousActiveTexture = 0;
|
||||
g_GLESFuncs.glGetIntegerv(GL_ACTIVE_TEXTURE, &previousActiveTexture);
|
||||
|
||||
// Copy the source rectangle into a scratch texture of its own format: both sides of
|
||||
// that blit are single-sampled, which ES does allow.
|
||||
Bool ok = true;
|
||||
if (s_texture == 0 || s_textureFormat != sourceFormat || s_textureWidth < srcWidth ||
|
||||
s_textureHeight < srcHeight) {
|
||||
if (s_texture != 0) {
|
||||
g_GLESFuncs.glDeleteTextures(1, &s_texture); // immutable storage cannot be resized
|
||||
s_texture = 0;
|
||||
}
|
||||
s_textureWidth = std::max(s_textureWidth, srcWidth);
|
||||
s_textureHeight = std::max(s_textureHeight, srcHeight);
|
||||
g_GLESFuncs.glGenTextures(1, &s_texture);
|
||||
if (s_texture == 0) {
|
||||
ok = false;
|
||||
} else {
|
||||
g_GLESFuncs.glActiveTexture(GL_TEXTURE0);
|
||||
g_GLESFuncs.glBindTexture(GL_TEXTURE_2D, s_texture);
|
||||
DrainBlitErrors();
|
||||
g_GLESFuncs.glTexStorage2D(GL_TEXTURE_2D, 1, sourceFormat, s_textureWidth, s_textureHeight);
|
||||
ok = g_GLESFuncs.glGetError() == GL_NO_ERROR;
|
||||
g_GLESFuncs.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
g_GLESFuncs.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
g_GLESFuncs.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
g_GLESFuncs.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
s_textureFormat = ok ? sourceFormat : 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
g_GLESFuncs.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, s_framebuffer);
|
||||
g_GLESFuncs.glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, ScratchAttachmentFor(sourceFormat), GL_TEXTURE_2D,
|
||||
s_texture, 0);
|
||||
ok = g_GLESFuncs.glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
|
||||
}
|
||||
if (ok) {
|
||||
const GLint left = std::min(srcX0, srcX1);
|
||||
const GLint bottom = std::min(srcY0, srcY1);
|
||||
DrainBlitErrors();
|
||||
g_GLESFuncs.glBlitFramebuffer(left, bottom, left + srcWidth, bottom + srcHeight, 0, 0, srcWidth, srcHeight,
|
||||
mask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT), GL_NEAREST);
|
||||
ok = g_GLESFuncs.glGetError() == GL_NO_ERROR;
|
||||
}
|
||||
|
||||
g_GLESFuncs.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, static_cast<GLuint>(previousDraw));
|
||||
g_GLESFuncs.glBindFramebuffer(GL_READ_FRAMEBUFFER, static_cast<GLuint>(previousRead));
|
||||
FramebufferImpl::InvalidateFramebufferBindingCache();
|
||||
if (!ok) {
|
||||
g_GLESFuncs.glActiveTexture(static_cast<GLenum>(previousActiveTexture));
|
||||
MGLOG_E("BlitFramebuffer: could not stage the source for the multisample replicate");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Everything below draws into the caller's multisample draw framebuffer, so the
|
||||
// pipeline state it depends on is saved and put back byte for byte - the sync layer's
|
||||
// shadow of the driver state has to stay true.
|
||||
GLint previousProgram = 0;
|
||||
GLint previousVertexArray = 0;
|
||||
GLint previousTexture = 0;
|
||||
GLint previousViewport[4] = {0, 0, 0, 0};
|
||||
GLint previousScissorBox[4] = {0, 0, 0, 0};
|
||||
GLboolean previousColorMask[4] = {GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE};
|
||||
GLint previousDepthFunc = GL_LESS;
|
||||
GLboolean previousDepthMask = GL_TRUE;
|
||||
GLint previousStencilFunc[2] = {GL_ALWAYS, GL_ALWAYS};
|
||||
GLint previousStencilRef[2] = {0, 0};
|
||||
GLint previousStencilValueMask[2] = {~0, ~0};
|
||||
GLint previousStencilWriteMask[2] = {~0, ~0};
|
||||
GLint previousStencilFail[2] = {GL_KEEP, GL_KEEP};
|
||||
GLint previousStencilDepthFail[2] = {GL_KEEP, GL_KEEP};
|
||||
GLint previousStencilPass[2] = {GL_KEEP, GL_KEEP};
|
||||
g_GLESFuncs.glGetIntegerv(GL_CURRENT_PROGRAM, &previousProgram);
|
||||
g_GLESFuncs.glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &previousVertexArray);
|
||||
g_GLESFuncs.glGetIntegerv(GL_TEXTURE_BINDING_2D, &previousTexture);
|
||||
g_GLESFuncs.glGetIntegerv(GL_VIEWPORT, previousViewport);
|
||||
g_GLESFuncs.glGetIntegerv(GL_SCISSOR_BOX, previousScissorBox);
|
||||
g_GLESFuncs.glGetBooleanv(GL_COLOR_WRITEMASK, previousColorMask);
|
||||
g_GLESFuncs.glGetIntegerv(GL_DEPTH_FUNC, &previousDepthFunc);
|
||||
g_GLESFuncs.glGetBooleanv(GL_DEPTH_WRITEMASK, &previousDepthMask);
|
||||
g_GLESFuncs.glGetIntegerv(GL_STENCIL_FUNC, &previousStencilFunc[0]);
|
||||
g_GLESFuncs.glGetIntegerv(GL_STENCIL_BACK_FUNC, &previousStencilFunc[1]);
|
||||
g_GLESFuncs.glGetIntegerv(GL_STENCIL_REF, &previousStencilRef[0]);
|
||||
g_GLESFuncs.glGetIntegerv(GL_STENCIL_BACK_REF, &previousStencilRef[1]);
|
||||
g_GLESFuncs.glGetIntegerv(GL_STENCIL_VALUE_MASK, &previousStencilValueMask[0]);
|
||||
g_GLESFuncs.glGetIntegerv(GL_STENCIL_BACK_VALUE_MASK, &previousStencilValueMask[1]);
|
||||
g_GLESFuncs.glGetIntegerv(GL_STENCIL_WRITEMASK, &previousStencilWriteMask[0]);
|
||||
g_GLESFuncs.glGetIntegerv(GL_STENCIL_BACK_WRITEMASK, &previousStencilWriteMask[1]);
|
||||
g_GLESFuncs.glGetIntegerv(GL_STENCIL_FAIL, &previousStencilFail[0]);
|
||||
g_GLESFuncs.glGetIntegerv(GL_STENCIL_BACK_FAIL, &previousStencilFail[1]);
|
||||
g_GLESFuncs.glGetIntegerv(GL_STENCIL_PASS_DEPTH_FAIL, &previousStencilDepthFail[0]);
|
||||
g_GLESFuncs.glGetIntegerv(GL_STENCIL_BACK_PASS_DEPTH_FAIL, &previousStencilDepthFail[1]);
|
||||
g_GLESFuncs.glGetIntegerv(GL_STENCIL_PASS_DEPTH_PASS, &previousStencilPass[0]);
|
||||
g_GLESFuncs.glGetIntegerv(GL_STENCIL_BACK_PASS_DEPTH_PASS, &previousStencilPass[1]);
|
||||
|
||||
struct CapabilityState {
|
||||
GLenum cap;
|
||||
GLboolean enabled;
|
||||
};
|
||||
CapabilityState capabilities[] = {
|
||||
{GL_SCISSOR_TEST, GL_FALSE}, {GL_DEPTH_TEST, GL_FALSE},
|
||||
{GL_STENCIL_TEST, GL_FALSE}, {GL_CULL_FACE, GL_FALSE},
|
||||
{GL_BLEND, GL_FALSE}, {GL_RASTERIZER_DISCARD, GL_FALSE},
|
||||
{GL_POLYGON_OFFSET_FILL, GL_FALSE}, {GL_SAMPLE_ALPHA_TO_COVERAGE, GL_FALSE},
|
||||
{GL_SAMPLE_COVERAGE, GL_FALSE}, {GL_SAMPLE_MASK, GL_FALSE},
|
||||
};
|
||||
for (CapabilityState& capability : capabilities) {
|
||||
capability.enabled = g_GLESFuncs.glIsEnabled(capability.cap);
|
||||
}
|
||||
|
||||
const GLint dstLeft = std::min(dstX0, dstX1);
|
||||
const GLint dstBottom = std::min(dstY0, dstY1);
|
||||
const Bool mirrorX = (srcX1 > srcX0) != (dstX1 > dstX0);
|
||||
const Bool mirrorY = (srcY1 > srcY0) != (dstY1 > dstY0);
|
||||
// The scratch holds the source rectangle at its origin, so the texture is larger than
|
||||
// the copied region: scale the [0,1] quad coordinates down to the region it occupies.
|
||||
const Float uvScaleX = static_cast<Float>(srcWidth) / static_cast<Float>(s_textureWidth);
|
||||
const Float uvScaleY = static_cast<Float>(srcHeight) / static_cast<Float>(s_textureHeight);
|
||||
const Float uvTransform[4] = {mirrorX ? -uvScaleX : uvScaleX, mirrorY ? -uvScaleY : uvScaleY,
|
||||
mirrorX ? uvScaleX : 0.0f, mirrorY ? uvScaleY : 0.0f};
|
||||
|
||||
g_GLESFuncs.glBindVertexArray(s_vertexArray);
|
||||
g_GLESFuncs.glActiveTexture(GL_TEXTURE0);
|
||||
g_GLESFuncs.glBindTexture(GL_TEXTURE_2D, s_texture);
|
||||
g_GLESFuncs.glViewport(dstLeft, dstBottom, dstWidth, dstHeight);
|
||||
g_GLESFuncs.glScissor(dstLeft, dstBottom, dstWidth, dstHeight);
|
||||
g_GLESFuncs.glEnable(GL_SCISSOR_TEST);
|
||||
g_GLESFuncs.glDisable(GL_CULL_FACE);
|
||||
g_GLESFuncs.glDisable(GL_BLEND);
|
||||
g_GLESFuncs.glDisable(GL_RASTERIZER_DISCARD);
|
||||
g_GLESFuncs.glDisable(GL_POLYGON_OFFSET_FILL);
|
||||
g_GLESFuncs.glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);
|
||||
g_GLESFuncs.glDisable(GL_SAMPLE_COVERAGE);
|
||||
g_GLESFuncs.glDisable(GL_SAMPLE_MASK);
|
||||
g_GLESFuncs.glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
|
||||
DrainBlitErrors();
|
||||
|
||||
if (wantDepth) {
|
||||
if (FormatHasStencil(sourceFormat)) {
|
||||
g_GLESFuncs.glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_DEPTH_COMPONENT);
|
||||
}
|
||||
g_GLESFuncs.glUseProgram(s_depthProgram);
|
||||
g_GLESFuncs.glUniform4f(s_depthUvTransform, uvTransform[0], uvTransform[1], uvTransform[2],
|
||||
uvTransform[3]);
|
||||
g_GLESFuncs.glEnable(GL_DEPTH_TEST);
|
||||
g_GLESFuncs.glDepthFunc(GL_ALWAYS);
|
||||
g_GLESFuncs.glDepthMask(GL_TRUE);
|
||||
g_GLESFuncs.glDisable(GL_STENCIL_TEST);
|
||||
g_GLESFuncs.glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
}
|
||||
|
||||
if (wantStencil) {
|
||||
g_GLESFuncs.glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);
|
||||
g_GLESFuncs.glUseProgram(s_stencilProgram);
|
||||
g_GLESFuncs.glUniform4f(s_stencilUvTransform, uvTransform[0], uvTransform[1], uvTransform[2],
|
||||
uvTransform[3]);
|
||||
g_GLESFuncs.glDisable(GL_DEPTH_TEST);
|
||||
g_GLESFuncs.glDepthMask(GL_FALSE);
|
||||
g_GLESFuncs.glEnable(GL_STENCIL_TEST);
|
||||
// The bit planes are written by ORing in the set bits, so the destination has to
|
||||
// start from zero. The blit overwrites the whole rectangle anyway, and the scissor
|
||||
// keeps the clear inside it.
|
||||
const GLint zero = 0;
|
||||
g_GLESFuncs.glStencilMask(0xFFu);
|
||||
g_GLESFuncs.glClearBufferiv(GL_STENCIL, 0, &zero);
|
||||
g_GLESFuncs.glStencilFunc(GL_ALWAYS, 0xFF, 0xFFu);
|
||||
g_GLESFuncs.glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
|
||||
for (Uint bit = 0; bit < 8; ++bit) {
|
||||
g_GLESFuncs.glStencilMask(1u << bit);
|
||||
g_GLESFuncs.glUniform1ui(s_stencilBit, 1u << bit);
|
||||
g_GLESFuncs.glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
}
|
||||
g_GLESFuncs.glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_DEPTH_COMPONENT);
|
||||
}
|
||||
|
||||
const Bool replicated = g_GLESFuncs.glGetError() == GL_NO_ERROR;
|
||||
|
||||
g_GLESFuncs.glUseProgram(static_cast<GLuint>(previousProgram));
|
||||
g_GLESFuncs.glBindTexture(GL_TEXTURE_2D, static_cast<GLuint>(previousTexture));
|
||||
g_GLESFuncs.glActiveTexture(static_cast<GLenum>(previousActiveTexture));
|
||||
g_GLESFuncs.glBindVertexArray(static_cast<GLuint>(previousVertexArray));
|
||||
g_GLESFuncs.glViewport(previousViewport[0], previousViewport[1], previousViewport[2], previousViewport[3]);
|
||||
g_GLESFuncs.glScissor(previousScissorBox[0], previousScissorBox[1], previousScissorBox[2],
|
||||
previousScissorBox[3]);
|
||||
g_GLESFuncs.glColorMask(previousColorMask[0], previousColorMask[1], previousColorMask[2],
|
||||
previousColorMask[3]);
|
||||
g_GLESFuncs.glDepthFunc(static_cast<GLenum>(previousDepthFunc));
|
||||
g_GLESFuncs.glDepthMask(previousDepthMask);
|
||||
const GLenum faces[2] = {GL_FRONT, GL_BACK};
|
||||
for (SizeT face = 0; face < 2; ++face) {
|
||||
g_GLESFuncs.glStencilFuncSeparate(faces[face], static_cast<GLenum>(previousStencilFunc[face]),
|
||||
previousStencilRef[face],
|
||||
static_cast<GLuint>(previousStencilValueMask[face]));
|
||||
g_GLESFuncs.glStencilOpSeparate(faces[face], static_cast<GLenum>(previousStencilFail[face]),
|
||||
static_cast<GLenum>(previousStencilDepthFail[face]),
|
||||
static_cast<GLenum>(previousStencilPass[face]));
|
||||
g_GLESFuncs.glStencilMaskSeparate(faces[face], static_cast<GLuint>(previousStencilWriteMask[face]));
|
||||
}
|
||||
for (const CapabilityState& capability : capabilities) {
|
||||
if (capability.enabled) {
|
||||
g_GLESFuncs.glEnable(capability.cap);
|
||||
} else {
|
||||
g_GLESFuncs.glDisable(capability.cap);
|
||||
}
|
||||
}
|
||||
// The per-draw-buffer colour masks are not covered by the non-indexed glColorMask above.
|
||||
for (Uint index = 0; index < MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS; ++index) {
|
||||
const BoolVec4& colorMask = RenderStateImpl::g_syncedRenderStateParameters.ColorMasks[index];
|
||||
if (g_GLESFuncs.glColorMaski) {
|
||||
g_GLESFuncs.glColorMaski(index, colorMask.x() ? GL_TRUE : GL_FALSE, colorMask.y() ? GL_TRUE : GL_FALSE,
|
||||
colorMask.z() ? GL_TRUE : GL_FALSE, colorMask.w() ? GL_TRUE : GL_FALSE);
|
||||
}
|
||||
}
|
||||
DrainBlitErrors();
|
||||
|
||||
if (!replicated) {
|
||||
MGLOG_E("BlitFramebuffer: multisample replicate fallback failed");
|
||||
}
|
||||
return replicated;
|
||||
}
|
||||
|
||||
static void IssueBlitWithResolveFallback(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0,
|
||||
GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) {
|
||||
DrainBlitErrors();
|
||||
g_GLESFuncs.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
|
||||
if (g_GLESFuncs.glGetError() == GL_NO_ERROR || (mask & GL_COLOR_BUFFER_BIT) == 0) {
|
||||
if (g_GLESFuncs.glGetError() == GL_NO_ERROR) {
|
||||
return;
|
||||
}
|
||||
// Only the multisample-resolve format mismatch is worth a second attempt; a
|
||||
// source that is not multisampled would have hit the same restriction on desktop.
|
||||
// Two ES restrictions desktop GL does not have are worth a second attempt: a
|
||||
// multisample resolve that also converts colour format, and any blit into a
|
||||
// multisample draw framebuffer. Both need to know how the two sides are sampled.
|
||||
GLint readSamples = 0;
|
||||
GLint drawSamples = 0;
|
||||
{
|
||||
@@ -2390,7 +2858,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
g_GLESFuncs.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, static_cast<GLuint>(previousDraw));
|
||||
FramebufferImpl::InvalidateFramebufferBindingCache();
|
||||
}
|
||||
if (readSamples <= 0 || drawSamples > 0) {
|
||||
if (readSamples <= 0 && drawSamples > 0) {
|
||||
// Single-sample source into a multisample destination: ES rejects the call
|
||||
// outright, desktop GL replicates the source sample into every destination one.
|
||||
if (ReplicateBlitIntoMultisampleDraw(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask)) {
|
||||
if ((mask & GL_COLOR_BUFFER_BIT) != 0) {
|
||||
MGLOG_E("BlitFramebuffer: colour replicate into a multisample draw framebuffer is not emulated");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (readSamples <= 0 || drawSamples > 0 || (mask & GL_COLOR_BUFFER_BIT) == 0) {
|
||||
return;
|
||||
}
|
||||
if (ResolveThenBlit(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, filter) &&
|
||||
|
||||
@@ -2405,7 +2405,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
MGLOG_D("%s(%s:%d) ES error %s", func, file, line, MG_Util::ConvertGLEnumToString(err).c_str());
|
||||
});
|
||||
|
||||
const auto& swizzleParams = stateTextureObject->GetAllSwizzleParams();
|
||||
// A three-channel format widened to four for a multisample target (see
|
||||
// NormalizePixelFormat) gains an alpha channel the frontend format does not have, and
|
||||
// whatever the draw that filled it wrote there is not what GL would report: a format
|
||||
// without alpha reads back as 1.0. Answer the ALPHA swizzle source with ONE so the
|
||||
// promotion stays invisible, composed with the swizzle the application asked for.
|
||||
Vec4<TextureSwizzleParam> swizzleParams = stateTextureObject->GetAllSwizzleParams();
|
||||
if (TextureImpl::BackendTextureFormatAddsAlpha(stateTextureObject->GetFormat(), targetInternal)) {
|
||||
for (SizeT channel = 0; channel < 4; ++channel) {
|
||||
if (swizzleParams[channel] == TextureSwizzleParam::Alpha) {
|
||||
swizzleParams[channel] = TextureSwizzleParam::One;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (swizzleParams != m_cacheSwizzleParams) {
|
||||
#define SYNC_TEX_SWIZZLE_PARAM_IF_CHANGED(func, glEnum) \
|
||||
if (m_cacheSwizzleParams.func != swizzleParams.func) { \
|
||||
|
||||
@@ -48,15 +48,40 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return options;
|
||||
}
|
||||
|
||||
Flags<PixelFormatNormalizeOptionBit> GetRuntimeFallbackNormalizeOptions(GLenum requestedInternalFormat) {
|
||||
Flags<PixelFormatNormalizeOptionBit>
|
||||
GetRuntimeFallbackNormalizeOptions(GLenum requestedInternalFormat,
|
||||
Flags<PixelFormatNormalizeOptionBit> extraOptions) {
|
||||
using namespace MG_Util::TextureFormatProcessor;
|
||||
const Flags<PixelFormatNormalizeOptionBit> forcedOptions =
|
||||
GetApplicablePixelFormatNormalizeOptions(requestedInternalFormat, GetForcedPixelFormatNormalizeOptions());
|
||||
const Flags<PixelFormatNormalizeOptionBit> forcedOptions = GetApplicablePixelFormatNormalizeOptions(
|
||||
requestedInternalFormat, GetForcedPixelFormatNormalizeOptions() | extraOptions);
|
||||
if (forcedOptions) {
|
||||
return forcedOptions;
|
||||
}
|
||||
return GetApplicablePixelFormatNormalizeOptions(requestedInternalFormat,
|
||||
GetDriverPixelFormatNormalizeOptions());
|
||||
return GetApplicablePixelFormatNormalizeOptions(
|
||||
requestedInternalFormat, GetDriverPixelFormatNormalizeOptions() | extraOptions);
|
||||
}
|
||||
|
||||
// Multisample textures can only ever be rendered into, never uploaded to, so a fallback
|
||||
// format for them has to stay colour-renderable - a three-channel float fallback is a legal
|
||||
// ES texture format but not a legal multisample storage format. Widening to four channels
|
||||
// is safe here precisely because there is no transfer path that would have to expand
|
||||
// three-channel client data, and the alpha the draw writes for a three-channel source is
|
||||
// already the 1.0 the frontend format implies.
|
||||
Bool TargetRequiresRenderableFormat(SizeT targetIndex) {
|
||||
return targetIndex == static_cast<SizeT>(TextureTarget::Texture2DMultisample) ||
|
||||
targetIndex == static_cast<SizeT>(TextureTarget::Texture2DMultisampleArray);
|
||||
}
|
||||
|
||||
Flags<PixelFormatNormalizeOptionBit> GetRenderTargetNormalizeOptions(SizeT targetIndex) {
|
||||
Flags<PixelFormatNormalizeOptionBit> options;
|
||||
if (!TargetRequiresRenderableFormat(targetIndex)) {
|
||||
return options;
|
||||
}
|
||||
options |= PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget;
|
||||
if (!g_GLESCapabilities.SupportsRenderSnorm || !g_GLESCapabilities.SupportsNorm16Texture) {
|
||||
options |= PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
Bool HasCachedFormatCapability(TextureInternalFormat internalFormat,
|
||||
@@ -116,7 +141,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
const GLenum requestedInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(internalFormat);
|
||||
Flags<PixelFormatNormalizeOptionBit> options;
|
||||
if (!pActiveBackendObject || ShouldUseCaveatFormat(internalFormat, targetIndex)) {
|
||||
options = GetRuntimeFallbackNormalizeOptions(requestedInternalFormat);
|
||||
options = GetRuntimeFallbackNormalizeOptions(requestedInternalFormat,
|
||||
GetRenderTargetNormalizeOptions(targetIndex));
|
||||
}
|
||||
NormalizePixelFormat(requestedInternalFormat, options, outInternalFormat, outFormat, outType);
|
||||
}
|
||||
@@ -151,6 +177,22 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Bool ShouldUseCaveatRenderbufferFormat(TextureInternalFormat internalFormat) {
|
||||
return ShouldUseCaveatFormat(internalFormat, GetRenderbufferFormatCapabilityTargetIndex());
|
||||
}
|
||||
|
||||
Bool BackendTextureFormatAddsAlpha(TextureInternalFormat internalFormat, TextureTarget target) {
|
||||
const SizeT targetIndex =
|
||||
target == TextureTarget::Unknown ? kFormatCapabilityTargetCount : GetFormatCapabilityTargetIndex(target);
|
||||
if (!TargetRequiresRenderableFormat(targetIndex)) {
|
||||
return false;
|
||||
}
|
||||
if (pActiveBackendObject && !ShouldUseCaveatFormat(internalFormat, targetIndex)) {
|
||||
return false;
|
||||
}
|
||||
const GLenum requestedInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(internalFormat);
|
||||
const Flags<PixelFormatNormalizeOptionBit> options =
|
||||
GetRuntimeFallbackNormalizeOptions(requestedInternalFormat,
|
||||
GetRenderTargetNormalizeOptions(targetIndex));
|
||||
return static_cast<Bool>(options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget);
|
||||
}
|
||||
} // namespace TextureImpl
|
||||
namespace PrgramImpl {
|
||||
String ProcessOutColorLocations(const String& glslCode) {
|
||||
|
||||
@@ -40,6 +40,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
void GenerateRenderbufferFormatInfo(TextureInternalFormat internalFormat, GLenum* outInternalFormat,
|
||||
GLenum* outFormat, GLenum* outType);
|
||||
Bool ShouldUseCaveatTextureFormat(TextureInternalFormat internalFormat, TextureTarget target);
|
||||
|
||||
// True when the format the texture is actually created with has an alpha channel the
|
||||
// frontend format does not (the three-channel multisample widening). GL reads such a
|
||||
// channel back as 1.0, so any swizzle source of ALPHA has to be answered with ONE.
|
||||
Bool BackendTextureFormatAddsAlpha(TextureInternalFormat internalFormat, TextureTarget target);
|
||||
Bool ShouldUseCaveatRenderbufferFormat(TextureInternalFormat internalFormat);
|
||||
} // namespace TextureImpl
|
||||
|
||||
|
||||
@@ -101,7 +101,14 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
// shared-exponent, SNORM, three-channel norm16/float32/sRGB and three-channel integer formats.
|
||||
// Desktop GL treats those as texture-only too (not in the GL 3.3 required-renderable list), so
|
||||
// reporting GL_FRAMEBUFFER_UNSUPPORTED for them is legal.
|
||||
Bool IsColorInternalFormatRenderable(TextureInternalFormat format) {
|
||||
//
|
||||
// `capabilityTargetIndex` is the row of the cache the attachment actually lives in;
|
||||
// kFormatCapabilityTargetCount asks about the format in general. Asking per target matters
|
||||
// because a capability recorded for one of them says nothing about the others: DirectGLES
|
||||
// widens three-channel formats to four channels to keep them renderable as *multisample*
|
||||
// storage, and a format that survives only through that substitution is still texture-only
|
||||
// on every ordinary target.
|
||||
Bool IsColorInternalFormatRenderable(TextureInternalFormat format, SizeT capabilityTargetIndex) {
|
||||
const SizeT formatIndex = static_cast<SizeT>(format);
|
||||
if (MG_Backend::pActiveBackendObject && formatIndex < MG_Backend::kFormatCapabilityFormatCount) {
|
||||
const auto& cache = MG_Backend::pActiveBackendObject->GetFormatCapabilities();
|
||||
@@ -113,8 +120,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
MG_Backend::FormatCapability::Creatable);
|
||||
}
|
||||
if (cachePopulated) {
|
||||
for (SizeT targetIndex = 0; targetIndex < MG_Backend::kFormatCapabilityTargetCount;
|
||||
++targetIndex) {
|
||||
const Bool singleTarget = capabilityTargetIndex < MG_Backend::kFormatCapabilityTargetCount;
|
||||
const SizeT firstTarget = singleTarget ? capabilityTargetIndex : 0;
|
||||
const SizeT lastTarget =
|
||||
singleTarget ? capabilityTargetIndex + 1 : MG_Backend::kFormatCapabilityTargetCount;
|
||||
for (SizeT targetIndex = firstTarget; targetIndex < lastTarget; ++targetIndex) {
|
||||
if (MG_Backend::HasFormatCapability(cache.FullCaps[targetIndex][formatIndex],
|
||||
MG_Backend::FormatCapability::FramebufferRenderable) ||
|
||||
MG_Backend::HasFormatCapability(cache.CaveatCaps[targetIndex][formatIndex],
|
||||
@@ -163,12 +173,17 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
const auto& attachment = attachments[i];
|
||||
if (!attachment.IsValid()) continue;
|
||||
TextureInternalFormat format = TextureInternalFormat::Unknown;
|
||||
SizeT capabilityTargetIndex = MG_Backend::kFormatCapabilityTargetCount;
|
||||
if (attachment.IsTexture() && attachment.GetTexture()) {
|
||||
format = attachment.GetTexture()->GetFormat();
|
||||
capabilityTargetIndex =
|
||||
MG_Backend::GetFormatCapabilityTargetIndex(attachment.GetTexture()->GetTarget());
|
||||
} else if (attachment.IsRenderbuffer() && attachment.GetRenderbuffer()) {
|
||||
format = attachment.GetRenderbuffer()->GetInternalFormat();
|
||||
capabilityTargetIndex = MG_Backend::GetRenderbufferFormatCapabilityTargetIndex();
|
||||
}
|
||||
if (format != TextureInternalFormat::Unknown && !IsColorInternalFormatRenderable(format)) {
|
||||
if (format != TextureInternalFormat::Unknown &&
|
||||
!IsColorInternalFormatRenderable(format, capabilityTargetIndex)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -824,6 +824,12 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
if (std::strcmp(extension, "GL_EXT_texture_norm16") == 0) {
|
||||
caps.SupportsNorm16Texture = true;
|
||||
}
|
||||
if (std::strcmp(extension, "GL_EXT_render_snorm") == 0) {
|
||||
caps.SupportsRenderSnorm = true;
|
||||
}
|
||||
if (std::strcmp(extension, "GL_EXT_sRGB_write_control") == 0) {
|
||||
caps.SupportsSrgbWriteControl = true;
|
||||
}
|
||||
if (std::strcmp(extension, "GL_EXT_texture_filter_anisotropic") == 0) {
|
||||
caps.SupportsTextureFilterAnisotropy = true;
|
||||
}
|
||||
|
||||
@@ -1031,6 +1031,13 @@ namespace MobileGL {
|
||||
String GLESShadingLanguageVersionString;
|
||||
Bool SupportsPersistentMapping = false;
|
||||
Bool SupportsNorm16Texture = false;
|
||||
// GL_EXT_render_snorm is present, so the signed-normalized formats are colour-renderable
|
||||
// (and usable as multisample texture storage) rather than texture-only.
|
||||
Bool SupportsRenderSnorm = false;
|
||||
// GL_EXT_sRGB_write_control is present, so GL_FRAMEBUFFER_SRGB can be turned off.
|
||||
// GLES has no such switch in core: writes into an sRGB attachment are ALWAYS encoded,
|
||||
// while desktop GL leaves GL_FRAMEBUFFER_SRGB disabled by default and writes raw.
|
||||
Bool SupportsSrgbWriteControl = false;
|
||||
// GL_EXT_texture_filter_anisotropic is present, so sampler/texture
|
||||
// anisotropy may be forwarded without raising GL_INVALID_ENUM in GLES.
|
||||
Bool SupportsTextureFilterAnisotropy = false;
|
||||
|
||||
@@ -29,11 +29,16 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
||||
case GL_RGB12: // stored as RGB16 (see NormalizePixelFormat)
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoNorm16;
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoRgb16;
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget;
|
||||
break;
|
||||
case GL_RGB16_SNORM:
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoRGB16Snorm;
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoNorm16;
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoSnorm16;
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget;
|
||||
if (options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget) {
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget;
|
||||
}
|
||||
break;
|
||||
case GL_RGBA16_SNORM:
|
||||
case GL_RG16_SNORM:
|
||||
@@ -46,6 +51,9 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoRGBA8Snorm;
|
||||
break;
|
||||
case GL_RGB8_SNORM:
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoSnorm8;
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget;
|
||||
break;
|
||||
case GL_RG8_SNORM:
|
||||
case GL_R8_SNORM:
|
||||
applicableOptions |= options & PixelFormatNormalizeOptionBit::NoSnorm8;
|
||||
@@ -87,6 +95,13 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
||||
*outInternalFormat = internalFormat;
|
||||
break;
|
||||
case GL_RGB16:
|
||||
if (options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget) {
|
||||
// GL_RGB32F is a legal ES texture format but is not colour-renderable, so
|
||||
// glTexStorage2DMultisample rejects it and the attachment ends up with no
|
||||
// storage at all.
|
||||
*outInternalFormat = GL_RGBA32F;
|
||||
break;
|
||||
}
|
||||
if ((options & PixelFormatNormalizeOptionBit::NoNorm16) ||
|
||||
(options & PixelFormatNormalizeOptionBit::NoRgb16)) {
|
||||
*outInternalFormat = GL_RGB32F;
|
||||
@@ -117,6 +132,14 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
||||
*outInternalFormat = internalFormat;
|
||||
break;
|
||||
case GL_RGB16_SNORM:
|
||||
if (options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget) {
|
||||
// A half float loses the low bits of a 16-bit SNORM channel, so keep the
|
||||
// signed-normalized encoding whenever the driver can render to it.
|
||||
*outInternalFormat = (options & PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget)
|
||||
? GL_RGBA16F
|
||||
: GL_RGBA16_SNORM;
|
||||
break;
|
||||
}
|
||||
if ((options & PixelFormatNormalizeOptionBit::NoNorm16) ||
|
||||
(options & PixelFormatNormalizeOptionBit::NoRGB16Snorm) ||
|
||||
(options & PixelFormatNormalizeOptionBit::NoSnorm16)) {
|
||||
@@ -150,6 +173,10 @@ namespace MobileGL::MG_Util::TextureFormatProcessor {
|
||||
*outInternalFormat = internalFormat;
|
||||
break;
|
||||
case GL_RGB8_SNORM:
|
||||
if (options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget) {
|
||||
*outInternalFormat = GL_RGBA16F;
|
||||
break;
|
||||
}
|
||||
if (options & PixelFormatNormalizeOptionBit::NoSnorm8) {
|
||||
*outInternalFormat = GL_RGB16F;
|
||||
break;
|
||||
|
||||
@@ -18,6 +18,17 @@ namespace MobileGL {
|
||||
NoDepthComponent32 = 1 << 4,
|
||||
NoRGBA8Snorm = 1 << 5,
|
||||
NoRGB16Snorm = 1 << 6,
|
||||
// The target must be colour-renderable and ES has no renderable three-channel
|
||||
// form of the requested format, so it has to be widened to the four-channel one.
|
||||
// Only meaningful for multisample textures: those can never be uploaded to, only
|
||||
// rendered into, so the extra alpha comes from the draw (1.0 for an RGB source)
|
||||
// and no transfer path has to expand three-channel client data.
|
||||
NoThreeChannelRenderTarget = 1 << 7,
|
||||
// Pairs with the bit above: the widened four-channel format has to stay renderable AND
|
||||
// keep 16-bit signed-normalized precision, which needs both EXT_texture_norm16 and
|
||||
// EXT_render_snorm. Without them the only renderable widening left is a half float, whose
|
||||
// 11-bit mantissa cannot represent a 16-bit SNORM channel exactly.
|
||||
NoSnorm16RenderTarget = 1 << 8,
|
||||
None = 0,
|
||||
};
|
||||
namespace MG_Util::TextureFormatProcessor {
|
||||
|
||||
Reference in New Issue
Block a user