Files
test/intern/ghost/intern/GHOST_ContextCGL.mm
2019-09-27 12:37:53 +10:00

574 lines
16 KiB
Plaintext

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2013 Blender Foundation.
* All rights reserved.
*/
/** \file ghost/intern/GHOST_ContextCGL.mm
* \ingroup GHOST
*
* Definition of GHOST_ContextCGL class.
*/
#include "GHOST_ContextCGL.h"
#include <Cocoa/Cocoa.h>
#include <QuartzCore/QuartzCore.h>
#include <Metal/Metal.h>
#include <vector>
#include <cassert>
static void ghost_fatal_error_dialog(const char *msg)
{
@autoreleasepool {
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);
}
attribs.push_back(NSOpenGLPFAAllowOfflineRenderers); // for automatic GPU switching
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
static bool softwareGL = getenv("BLENDER_SOFTWAREGL"); // command-line argument would be better
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 vsync, to avoid tearing artifacts */
[m_openGLContext setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
}
#endif
initContextGLEW();
if (m_metalView) {
if (m_defaultFramebuffer == 0) {
// Create a virtual framebuffer
[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()
{
@autoreleasepool {
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()
{
@autoreleasepool {
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];
}
}