Files
test/intern/ghost/intern/GHOST_ContextCGL.mm
Campbell Barton c434782e3a File headers: SPDX License migration
Use a shorter/simpler license convention, stops the header taking so
much space.

Follow the SPDX license specification: https://spdx.org/licenses

- C/C++/objc/objc++
- Python
- Shell Scripts
- CMake, GNUmakefile

While most of the source tree has been included

- `./extern/` was left out.
- `./intern/cycles` & `./intern/atomic` are also excluded because they
  use different header conventions.

doc/license/SPDX-license-identifiers.txt has been added to list SPDX all
used identifiers.

See P2788 for the script that automated these edits.

Reviewed By: brecht, mont29, sergey

Ref D14069
2022-02-11 09:14:36 +11:00

563 lines
15 KiB
Plaintext

/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2013 Blender Foundation. All rights reserved. */
/** \file
* \ingroup GHOST
*
* Definition of GHOST_ContextCGL class.
*/
#include "GHOST_ContextCGL.h"
#include <Cocoa/Cocoa.h>
#include <Metal/Metal.h>
#include <QuartzCore/QuartzCore.h>
#include <cassert>
#include <vector>
static void ghost_fatal_error_dialog(const char *msg)
{
/* clang-format off */
@autoreleasepool {
/* clang-format on */
NSString *message = [NSString stringWithFormat:@"Error opening window:\n%s", msg];
NSAlert *alert = [[NSAlert alloc] init];
[alert addButtonWithTitle:@"Quit"];
[alert setMessageText:@"Blender"];
[alert setInformativeText:message];
[alert setAlertStyle:NSAlertStyleCritical];
[alert runModal];
}
exit(1);
}
NSOpenGLContext *GHOST_ContextCGL::s_sharedOpenGLContext = nil;
int GHOST_ContextCGL::s_sharedCount = 0;
GHOST_ContextCGL::GHOST_ContextCGL(bool stereoVisual,
NSView *metalView,
CAMetalLayer *metalLayer,
NSOpenGLView *openGLView)
: GHOST_Context(stereoVisual),
m_metalView(metalView),
m_metalLayer(metalLayer),
m_metalCmdQueue(nil),
m_metalRenderPipeline(nil),
m_openGLView(openGLView),
m_openGLContext(nil),
m_defaultFramebuffer(0),
m_defaultFramebufferMetalTexture(nil),
m_debug(false)
{
#if defined(WITH_GL_PROFILE_CORE)
m_coreProfile = true;
#else
m_coreProfile = false;
#endif
if (m_metalView) {
metalInit();
}
}
GHOST_ContextCGL::~GHOST_ContextCGL()
{
metalFree();
if (m_openGLContext != nil) {
if (m_openGLContext == [NSOpenGLContext currentContext]) {
[NSOpenGLContext clearCurrentContext];
if (m_openGLView) {
[m_openGLView clearGLContext];
}
}
if (m_openGLContext != s_sharedOpenGLContext || s_sharedCount == 1) {
assert(s_sharedCount > 0);
s_sharedCount--;
if (s_sharedCount == 0)
s_sharedOpenGLContext = nil;
[m_openGLContext release];
}
}
}
GHOST_TSuccess GHOST_ContextCGL::swapBuffers()
{
if (m_openGLContext != nil) {
if (m_metalView) {
metalSwapBuffers();
}
else if (m_openGLView) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[m_openGLContext flushBuffer];
[pool drain];
}
return GHOST_kSuccess;
}
else {
return GHOST_kFailure;
}
}
GHOST_TSuccess GHOST_ContextCGL::setSwapInterval(int interval)
{
if (m_openGLContext != nil) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[m_openGLContext setValues:&interval forParameter:NSOpenGLCPSwapInterval];
[pool drain];
return GHOST_kSuccess;
}
else {
return GHOST_kFailure;
}
}
GHOST_TSuccess GHOST_ContextCGL::getSwapInterval(int &intervalOut)
{
if (m_openGLContext != nil) {
GLint interval;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[m_openGLContext getValues:&interval forParameter:NSOpenGLCPSwapInterval];
[pool drain];
intervalOut = static_cast<int>(interval);
return GHOST_kSuccess;
}
else {
return GHOST_kFailure;
}
}
GHOST_TSuccess GHOST_ContextCGL::activateDrawingContext()
{
if (m_openGLContext != nil) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[m_openGLContext makeCurrentContext];
[pool drain];
return GHOST_kSuccess;
}
else {
return GHOST_kFailure;
}
}
GHOST_TSuccess GHOST_ContextCGL::releaseDrawingContext()
{
if (m_openGLContext != nil) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[NSOpenGLContext clearCurrentContext];
[pool drain];
return GHOST_kSuccess;
}
else {
return GHOST_kFailure;
}
}
unsigned int GHOST_ContextCGL::getDefaultFramebuffer()
{
return m_defaultFramebuffer;
}
GHOST_TSuccess GHOST_ContextCGL::updateDrawingContext()
{
if (m_openGLContext != nil) {
if (m_metalView) {
metalUpdateFramebuffer();
}
else if (m_openGLView) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[m_openGLContext update];
[pool drain];
}
return GHOST_kSuccess;
}
else {
return GHOST_kFailure;
}
}
static void makeAttribList(std::vector<NSOpenGLPixelFormatAttribute> &attribs,
bool coreProfile,
bool stereoVisual,
bool needAlpha,
bool softwareGL)
{
attribs.clear();
attribs.push_back(NSOpenGLPFAOpenGLProfile);
attribs.push_back(coreProfile ? NSOpenGLProfileVersion3_2Core : NSOpenGLProfileVersionLegacy);
/* Pixel Format Attributes for the windowed NSOpenGLContext. */
attribs.push_back(NSOpenGLPFADoubleBuffer);
if (softwareGL) {
attribs.push_back(NSOpenGLPFARendererID);
attribs.push_back(kCGLRendererGenericFloatID);
}
else {
attribs.push_back(NSOpenGLPFAAccelerated);
attribs.push_back(NSOpenGLPFANoRecovery);
}
if (stereoVisual)
attribs.push_back(NSOpenGLPFAStereo);
if (needAlpha) {
attribs.push_back(NSOpenGLPFAAlphaSize);
attribs.push_back((NSOpenGLPixelFormatAttribute)8);
}
attribs.push_back((NSOpenGLPixelFormatAttribute)0);
}
GHOST_TSuccess GHOST_ContextCGL::initializeDrawingContext()
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
#ifdef GHOST_OPENGL_ALPHA
static const bool needAlpha = true;
#else
static const bool needAlpha = false;
#endif
/* Command-line argument would be better. */
static bool softwareGL = getenv("BLENDER_SOFTWAREGL");
std::vector<NSOpenGLPixelFormatAttribute> attribs;
attribs.reserve(40);
makeAttribList(attribs, m_coreProfile, m_stereoVisual, needAlpha, softwareGL);
NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:&attribs[0]];
if (pixelFormat == nil) {
goto error;
}
m_openGLContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat
shareContext:s_sharedOpenGLContext];
[pixelFormat release];
[m_openGLContext makeCurrentContext];
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];
}
#endif
initContextGLEW();
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++;
[pool drain];
return GHOST_kSuccess;
error:
[pixelFormat release];
[pool drain];
return GHOST_kFailure;
}
GHOST_TSuccess GHOST_ContextCGL::releaseNativeHandles()
{
m_openGLContext = nil;
m_openGLView = nil;
m_metalView = nil;
return GHOST_kSuccess;
}
/* OpenGL on Metal
*
* Use Metal layer to avoid Viewport lagging on macOS, see T60043. */
static const MTLPixelFormat METAL_FRAMEBUFFERPIXEL_FORMAT = MTLPixelFormatBGRA8Unorm;
static const OSType METAL_CORE_VIDEO_PIXEL_FORMAT = kCVPixelFormatType_32BGRA;
void GHOST_ContextCGL::metalInit()
{
/* clang-format off */
@autoreleasepool {
/* clang-format on */
id<MTLDevice> device = m_metalLayer.device;
/* Create a command queue for blit/present operation. */
m_metalCmdQueue = (MTLCommandQueue *)[device newCommandQueue];
[m_metalCmdQueue retain];
/* Create shaders for blit operation. */
NSString *source = @R"msl(
using namespace metal;
struct Vertex {
float4 position [[position]];
float2 texCoord [[attribute(0)]];
};
vertex Vertex vertex_shader(uint v_id [[vertex_id]]) {
Vertex vtx;
vtx.position.x = float(v_id & 1) * 4.0 - 1.0;
vtx.position.y = float(v_id >> 1) * 4.0 - 1.0;
vtx.position.z = 0.0;
vtx.position.w = 1.0;
vtx.texCoord = vtx.position.xy * 0.5 + 0.5;
return vtx;
}
constexpr sampler s {};
fragment float4 fragment_shader(Vertex v [[stage_in]],
texture2d<float> t [[texture(0)]]) {
return t.sample(s, v.texCoord);
}
)msl";
MTLCompileOptions *options = [[[MTLCompileOptions alloc] init] autorelease];
options.languageVersion = MTLLanguageVersion1_1;
NSError *error = nil;
id<MTLLibrary> library = [device newLibraryWithSource:source options:options error:&error];
if (error) {
ghost_fatal_error_dialog(
"GHOST_ContextCGL::metalInit: newLibraryWithSource:options:error: failed!");
}
/* Create a render pipeline for blit operation. */
MTLRenderPipelineDescriptor *desc = [[[MTLRenderPipelineDescriptor alloc] init] autorelease];
desc.fragmentFunction = [library newFunctionWithName:@"fragment_shader"];
desc.vertexFunction = [library newFunctionWithName:@"vertex_shader"];
[desc.colorAttachments objectAtIndexedSubscript:0].pixelFormat = METAL_FRAMEBUFFERPIXEL_FORMAT;
m_metalRenderPipeline = (MTLRenderPipelineState *)[device
newRenderPipelineStateWithDescriptor:desc
error:&error];
if (error) {
ghost_fatal_error_dialog(
"GHOST_ContextCGL::metalInit: newRenderPipelineStateWithDescriptor:error: failed!");
}
}
}
void GHOST_ContextCGL::metalFree()
{
if (m_metalCmdQueue) {
[m_metalCmdQueue release];
}
if (m_metalRenderPipeline) {
[m_metalRenderPipeline release];
}
if (m_defaultFramebufferMetalTexture) {
[m_defaultFramebufferMetalTexture release];
}
}
void GHOST_ContextCGL::metalInitFramebuffer()
{
glGenFramebuffers(1, &m_defaultFramebuffer);
updateDrawingContext();
glBindFramebuffer(GL_FRAMEBUFFER, m_defaultFramebuffer);
}
void GHOST_ContextCGL::metalUpdateFramebuffer()
{
assert(m_defaultFramebuffer != 0);
NSRect bounds = [m_metalView bounds];
NSSize backingSize = [m_metalView convertSizeToBacking:bounds.size];
size_t width = (size_t)backingSize.width;
size_t height = (size_t)backingSize.height;
{
/* Test if there is anything to update */
id<MTLTexture> tex = (id<MTLTexture>)m_defaultFramebufferMetalTexture;
if (tex && tex.width == width && tex.height == height) {
return;
}
}
activateDrawingContext();
NSDictionary *cvPixelBufferProps = @{
(__bridge NSString *)kCVPixelBufferOpenGLCompatibilityKey : @YES,
(__bridge NSString *)kCVPixelBufferMetalCompatibilityKey : @YES,
};
CVPixelBufferRef cvPixelBuffer = nil;
CVReturn cvret = CVPixelBufferCreate(kCFAllocatorDefault,
width,
height,
METAL_CORE_VIDEO_PIXEL_FORMAT,
(__bridge CFDictionaryRef)cvPixelBufferProps,
&cvPixelBuffer);
if (cvret != kCVReturnSuccess) {
ghost_fatal_error_dialog(
"GHOST_ContextCGL::metalUpdateFramebuffer: CVPixelBufferCreate failed!");
}
/* Create an OpenGL texture. */
CVOpenGLTextureCacheRef cvGLTexCache = nil;
cvret = CVOpenGLTextureCacheCreate(kCFAllocatorDefault,
nil,
m_openGLContext.CGLContextObj,
m_openGLContext.pixelFormat.CGLPixelFormatObj,
nil,
&cvGLTexCache);
if (cvret != kCVReturnSuccess) {
ghost_fatal_error_dialog(
"GHOST_ContextCGL::metalUpdateFramebuffer: CVOpenGLTextureCacheCreate failed!");
}
CVOpenGLTextureRef cvGLTex = nil;
cvret = CVOpenGLTextureCacheCreateTextureFromImage(
kCFAllocatorDefault, cvGLTexCache, cvPixelBuffer, nil, &cvGLTex);
if (cvret != kCVReturnSuccess) {
ghost_fatal_error_dialog(
"GHOST_ContextCGL::metalUpdateFramebuffer: "
"CVOpenGLTextureCacheCreateTextureFromImage failed!");
}
unsigned int glTex;
glTex = CVOpenGLTextureGetName(cvGLTex);
/* Create a Metal texture. */
CVMetalTextureCacheRef cvMetalTexCache = nil;
cvret = CVMetalTextureCacheCreate(
kCFAllocatorDefault, nil, m_metalLayer.device, nil, &cvMetalTexCache);
if (cvret != kCVReturnSuccess) {
ghost_fatal_error_dialog(
"GHOST_ContextCGL::metalUpdateFramebuffer: CVMetalTextureCacheCreate failed!");
}
CVMetalTextureRef cvMetalTex = nil;
cvret = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
cvMetalTexCache,
cvPixelBuffer,
nil,
METAL_FRAMEBUFFERPIXEL_FORMAT,
width,
height,
0,
&cvMetalTex);
if (cvret != kCVReturnSuccess) {
ghost_fatal_error_dialog(
"GHOST_ContextCGL::metalUpdateFramebuffer: "
"CVMetalTextureCacheCreateTextureFromImage failed!");
}
MTLTexture *tex = (MTLTexture *)CVMetalTextureGetTexture(cvMetalTex);
if (!tex) {
ghost_fatal_error_dialog(
"GHOST_ContextCGL::metalUpdateFramebuffer: CVMetalTextureGetTexture failed!");
}
[m_defaultFramebufferMetalTexture release];
m_defaultFramebufferMetalTexture = [tex retain];
glBindFramebuffer(GL_FRAMEBUFFER, m_defaultFramebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, glTex, 0);
[m_metalLayer setDrawableSize:CGSizeMake((CGFloat)width, (CGFloat)height)];
CVPixelBufferRelease(cvPixelBuffer);
CVOpenGLTextureCacheRelease(cvGLTexCache);
CVOpenGLTextureRelease(cvGLTex);
CFRelease(cvMetalTexCache);
CFRelease(cvMetalTex);
}
void GHOST_ContextCGL::metalSwapBuffers()
{
/* clang-format off */
@autoreleasepool {
/* clang-format on */
updateDrawingContext();
glFlush();
assert(m_defaultFramebufferMetalTexture != 0);
id<CAMetalDrawable> drawable = [m_metalLayer nextDrawable];
if (!drawable) {
return;
}
id<MTLCommandBuffer> cmdBuffer = [m_metalCmdQueue commandBuffer];
MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
{
auto attachment = [passDescriptor.colorAttachments objectAtIndexedSubscript:0];
attachment.texture = drawable.texture;
attachment.loadAction = MTLLoadActionDontCare;
attachment.storeAction = MTLStoreActionStore;
}
id<MTLTexture> srcTexture = (id<MTLTexture>)m_defaultFramebufferMetalTexture;
{
id<MTLRenderCommandEncoder> enc = [cmdBuffer
renderCommandEncoderWithDescriptor:passDescriptor];
[enc setRenderPipelineState:(id<MTLRenderPipelineState>)m_metalRenderPipeline];
[enc setFragmentTexture:srcTexture atIndex:0];
[enc drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
[enc endEncoding];
}
[cmdBuffer presentDrawable:drawable];
[cmdBuffer commit];
}
}