MacOS: Resolve purple rendering artifacts in EEVEE materials by increasing sampler limit.

Enables a feature flag during OpenGL device initialisation on macOS, which increases the available number of texture samplers available for use within shaders. Enabling this flag removes purple rendering artifacts present in certain EEVEE materials, when the existing limit of 16 is exceeded.

This feature flag is supported on Apple Silicon and AMD GPUs, for devices supporting macOS 11.0+. Device initialisation first tests whether GL device creation with the flag is supported, if not, we fall back to standard initialisation.

Other solutions would not be trivial or incur additional performance overhead or feature limitations. Other workarounds, such as texture atlas's, could already be created by artists.

{F13245498}

{F13245497}

Reviewed By: jbakker

Maniphest Tasks: T57759, T63935

Differential Revision: https://developer.blender.org/D15336
This commit is contained in:
Jason Fielder 2022-09-06 07:55:21 +02:00 committed by Jeroen Bakker
parent d9db79dbe5
commit 32d19f7317
Notes: blender-bot 2023-02-14 06:49:54 +01:00
Referenced by issue #100749, Blender LTS: Maintenance Task 3.3
Referenced by issue #101298, Blender 3.3.1 crashes before Splash Screen in M1 Max
Referenced by issue #63935, Blender 2.80 EEVEE unable to render some materials on OSX due to the OpenGL implementation
Referenced by issue #57759, The eevee view model failed to load the texture, causing the model to be purple
1 changed files with 107 additions and 57 deletions

View File

@ -193,7 +193,8 @@ GHOST_TSuccess GHOST_ContextCGL::updateDrawingContext()
static void makeAttribList(std::vector<NSOpenGLPixelFormatAttribute> &attribs,
bool stereoVisual,
bool needAlpha,
bool softwareGL)
bool softwareGL,
bool increasedSamplerLimit)
{
attribs.clear();
@ -210,6 +211,12 @@ static void makeAttribList(std::vector<NSOpenGLPixelFormatAttribute> &attribs,
else {
attribs.push_back(NSOpenGLPFAAccelerated);
attribs.push_back(NSOpenGLPFANoRecovery);
/* Attempt to initialise device with extended sampler limit.
* Resolves EEVEE purple rendering artifacts on macOS. */
if (increasedSamplerLimit) {
attribs.push_back((NSOpenGLPixelFormatAttribute)400);
}
}
if (stereoVisual)
@ -225,80 +232,123 @@ static void makeAttribList(std::vector<NSOpenGLPixelFormatAttribute> &attribs,
GHOST_TSuccess GHOST_ContextCGL::initializeDrawingContext()
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
@autoreleasepool {
#ifdef GHOST_OPENGL_ALPHA
static const bool needAlpha = true;
static const bool needAlpha = true;
#else
static const bool needAlpha = false;
static const bool needAlpha = false;
#endif
/* Command-line argument would be better. */
static bool softwareGL = getenv("BLENDER_SOFTWAREGL");
/* Command-line argument would be better. */
static bool softwareGL = getenv("BLENDER_SOFTWAREGL");
std::vector<NSOpenGLPixelFormatAttribute> attribs;
attribs.reserve(40);
makeAttribList(attribs, m_stereoVisual, needAlpha, softwareGL);
NSOpenGLPixelFormat *pixelFormat = nil;
std::vector<NSOpenGLPixelFormatAttribute> attribs;
bool increasedSamplerLimit = false;
NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:&attribs[0]];
if (pixelFormat == nil) {
goto error;
}
/* Attempt to initialize device with increased sampler limit.
* If this is unsupported and initialization fails, initialize GL Context as normal.
*
* NOTE: This is not available when using the SoftwareGL path, or for Intel-based
* platforms. */
if (!softwareGL) {
if (@available(macos 11.0, *)) {
increasedSamplerLimit = true;
}
}
const int max_ctx_attempts = increasedSamplerLimit ? 2 : 1;
for (int ctx_create_attempt = 0; ctx_create_attempt < max_ctx_attempts; ctx_create_attempt++) {
m_openGLContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat
shareContext:s_sharedOpenGLContext];
[pixelFormat release];
attribs.clear();
attribs.reserve(40);
makeAttribList(attribs, m_stereoVisual, needAlpha, softwareGL, increasedSamplerLimit);
[m_openGLContext makeCurrentContext];
pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:&attribs[0]];
if (pixelFormat == nil) {
/* If pixel format creation fails when testing increased sampler limit,
* attempt intialisation again with feature disabled, otherwise, fail. */
if (increasedSamplerLimit) {
increasedSamplerLimit = false;
continue;
}
return GHOST_kFailure;
}
if (m_debug) {
GLint major = 0, minor = 0;
glGetIntegerv(GL_MAJOR_VERSION, &major);
glGetIntegerv(GL_MINOR_VERSION, &minor);
fprintf(stderr, "OpenGL version %d.%d%s\n", major, minor, softwareGL ? " (software)" : "");
fprintf(stderr, "Renderer: %s\n", glGetString(GL_RENDERER));
}
/* Attempt to create context. */
m_openGLContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat
shareContext:s_sharedOpenGLContext];
[pixelFormat release];
if (m_openGLContext == nil) {
/* If context creation fails when testing increased sampler limit,
* attempt re-creation with feature disabled. Otherwise, error. */
if (increasedSamplerLimit) {
increasedSamplerLimit = false;
continue;
}
/* Default context creation attempt failed. */
return GHOST_kFailure;
}
/* Created GL context successfully, activate. */
[m_openGLContext makeCurrentContext];
/* When increasing sampler limit, verify created context is a supported configuration. */
if (increasedSamplerLimit) {
const char *vendor = (const char *)glGetString(GL_VENDOR);
const char *renderer = (const char *)glGetString(GL_RENDERER);
/* If generated context type is unsupported, release existing context and
* fallback to creating a normal context below. */
if (strstr(vendor, "Intel") || strstr(renderer, "Software")) {
[m_openGLContext release];
m_openGLContext = nil;
increasedSamplerLimit = false;
continue;
}
}
}
if (m_debug) {
GLint major = 0, minor = 0;
glGetIntegerv(GL_MAJOR_VERSION, &major);
glGetIntegerv(GL_MINOR_VERSION, &minor);
fprintf(stderr, "OpenGL version %d.%d%s\n", major, minor, softwareGL ? " (software)" : "");
fprintf(stderr, "Renderer: %s\n", glGetString(GL_RENDERER));
}
#ifdef GHOST_WAIT_FOR_VSYNC
{
GLint swapInt = 1;
/* Wait for vertical-sync, to avoid tearing artifacts. */
[m_openGLContext setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
}
{
GLint swapInt = 1;
/* Wait for vertical-sync, to avoid tearing artifacts. */
[m_openGLContext setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
}
#endif
if (m_metalView) {
if (m_defaultFramebuffer == 0) {
/* Create a virtual frame-buffer. */
[m_openGLContext makeCurrentContext];
metalInitFramebuffer();
if (m_metalView) {
if (m_defaultFramebuffer == 0) {
/* Create a virtual frame-buffer. */
[m_openGLContext makeCurrentContext];
metalInitFramebuffer();
initClearGL();
}
}
else if (m_openGLView) {
[m_openGLView setOpenGLContext:m_openGLContext];
[m_openGLContext setView:m_openGLView];
initClearGL();
}
[m_openGLContext flushBuffer];
if (s_sharedCount == 0)
s_sharedOpenGLContext = m_openGLContext;
s_sharedCount++;
}
else if (m_openGLView) {
[m_openGLView setOpenGLContext:m_openGLContext];
[m_openGLContext setView:m_openGLView];
initClearGL();
}
[m_openGLContext flushBuffer];
if (s_sharedCount == 0)
s_sharedOpenGLContext = m_openGLContext;
s_sharedCount++;
[pool drain];
return GHOST_kSuccess;
error:
[pixelFormat release];
[pool drain];
return GHOST_kFailure;
}
GHOST_TSuccess GHOST_ContextCGL::releaseNativeHandles()