#include "common.h" #include "rendertarget.h" #import #import #include #include @interface IOSurfaceHolder : NSObject @end @implementation IOSurfaceHolder { @public IOSurfaceRef surface; @public AvnPixelSize size; @public bool hasContent; @public float scale; ComPtr _context; GLuint _framebuffer, _texture, _renderbuffer; } - (IOSurfaceHolder*) initWithSize: (AvnPixelSize) size withScale: (float)scale withOpenGlContext: (IAvnGlContext*) context { long bytesPerRow = IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, size.Width * 4); long allocSize = IOSurfaceAlignProperty(kIOSurfaceAllocSize, size.Height * bytesPerRow); NSDictionary* options = @{ (id)kIOSurfaceWidth: @(size.Width), (id)kIOSurfaceHeight: @(size.Height), (id)kIOSurfacePixelFormat: @((uint)'BGRA'), (id)kIOSurfaceBytesPerElement: @(4), (id)kIOSurfaceBytesPerRow: @(bytesPerRow), (id)kIOSurfaceAllocSize: @(allocSize), //(id)kIOSurfaceCacheMode: @(kIOMapWriteCombineCache), (id)kIOSurfaceElementWidth: @(1), (id)kIOSurfaceElementHeight: @(1) }; surface = IOSurfaceCreate((CFDictionaryRef)options); self->scale = scale; self->size = size; self->_context = context; self->hasContent = false; return self; } -(HRESULT) prepareForGlRender { if(_context == nil) return E_FAIL; if(CGLGetCurrentContext() != _context->GetNativeHandle()) return E_FAIL; if(_framebuffer == 0) glGenFramebuffersEXT(1, &_framebuffer); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _framebuffer); if(_texture == 0) { glGenTextures(1, &_texture); glBindTexture(GL_TEXTURE_RECTANGLE_EXT, _texture); CGLError res = CGLTexImageIOSurface2D((CGLContextObj)_context->GetNativeHandle(), GL_TEXTURE_RECTANGLE_EXT, GL_RGBA8, size.Width, size.Height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, surface, 0); glBindTexture(GL_TEXTURE_RECTANGLE_EXT, 0); if(res != 0) { glDeleteTextures(1, &_texture); _texture = 0; glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); return E_FAIL; } glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_EXT, _texture, 0); } if(_renderbuffer == 0) { glGenRenderbuffers(1, &_renderbuffer); glBindRenderbuffer(GL_RENDERBUFFER, _renderbuffer); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, size.Width, size.Height); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _renderbuffer); } return S_OK; } -(void) finishDraw { ComPtr release; _context->MakeCurrent(release.getPPV()); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); glFlush(); self->hasContent = true; } -(void) dealloc { if(_framebuffer != 0) { ComPtr release; _context->MakeCurrent(release.getPPV()); glDeleteFramebuffers(1, &_framebuffer); if(_texture != 0) glDeleteTextures(1, &_texture); if(_renderbuffer != 0) glDeleteRenderbuffers(1, &_renderbuffer); } if(surface != nullptr) { CFRelease(surface); } } @end static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* target); @implementation IOSurfaceRenderTarget { CALayer* _layer; @public IOSurfaceHolder* surface; @public NSObject* lock; ComPtr _glContext; } - (IOSurfaceRenderTarget*) initWithOpenGlContext: (IAvnGlContext*) context; { self = [super init]; _glContext = context; lock = [NSObject new]; surface = nil; [self resize:{1,1} withScale: 1]; return self; } - (AvnPixelSize) pixelSize { return {1, 1}; } - (CALayer *)layer { return _layer; } - (void)resize:(AvnPixelSize)size withScale: (float) scale{ if(size.Height <= 0) size.Height = 1; if(size.Width <= 0) size.Width = 1; @synchronized (lock) { if(surface == nil || surface->size.Width != size.Width || surface->size.Height != size.Height || surface->scale != scale) { surface = [[IOSurfaceHolder alloc] initWithSize:size withScale:scale withOpenGlContext:_glContext.getRaw()]; [self updateLayer]; } } } - (void)updateLayer { if ([NSThread isMainThread]) { @synchronized (lock) { if(_layer == nil) return; if(!surface->hasContent) return; [CATransaction begin]; [_layer setContents: nil]; if(surface != nil) { [_layer setContentsScale: surface->scale]; [_layer setContents: (__bridge IOSurface*) surface->surface]; } [CATransaction commit]; [CATransaction flush]; } } else dispatch_async(dispatch_get_main_queue(), ^{ [self updateLayer]; }); } - (void) setNewLayer:(CALayer *)layer { _layer = layer; [self updateLayer]; } - (HRESULT)setSwFrame:(AvnFramebuffer *)fb { @synchronized (lock) { if(fb->PixelFormat == AvnPixelFormat::kAvnRgb565) return E_INVALIDARG; if(surface == nil) return E_FAIL; IOSurfaceRef surf = surface->surface; if(IOSurfaceLock(surf, 0, nil)) return E_FAIL; size_t w = MIN(fb->Width, IOSurfaceGetWidth(surf)); size_t h = MIN(fb->Height, IOSurfaceGetHeight(surf)); size_t wbytes = w*4; size_t sstride = IOSurfaceGetBytesPerRow(surf); size_t fstride = fb->Stride; char*pSurface = (char*)IOSurfaceGetBaseAddress(surf); char*pFb = (char*)fb->Data; for(size_t y = 0; y < h; y++) { memcpy(pSurface + y*sstride, pFb + y*fstride, wbytes); } IOSurfaceUnlock(surf, 0, nil); surface->hasContent = true; [self updateLayer]; return S_OK; } } -(IAvnGlSurfaceRenderTarget*) createSurfaceRenderTarget { return CreateGlRenderTarget(self); } @end class AvnGlRenderingSession : public ComSingleObject { ComPtr _releaseContext; IOSurfaceRenderTarget* _target; IOSurfaceHolder* _surface; public: FORWARD_IUNKNOWN() AvnGlRenderingSession(IOSurfaceRenderTarget* target, ComPtr releaseContext) { _target = target; // This happens in a synchronized block set up by AvnRenderTarget, so we take the current surface for this // particular render session _surface = _target->surface; _releaseContext = releaseContext; } virtual HRESULT GetPixelSize(AvnPixelSize* ret) override { START_COM_CALL; if(!_surface) return E_FAIL; *ret = _surface->size; return S_OK; } virtual HRESULT GetScaling(double* ret) override { START_COM_CALL; if(!_surface) return E_FAIL; *ret = _surface->scale; return S_OK; } virtual ~AvnGlRenderingSession() { [_surface finishDraw]; [_target updateLayer]; _releaseContext = nil; } }; class AvnGlRenderTarget : public ComSingleObject { IOSurfaceRenderTarget* _target; public: FORWARD_IUNKNOWN() AvnGlRenderTarget(IOSurfaceRenderTarget* target) { _target = target; } virtual HRESULT BeginDrawing(IAvnGlSurfaceRenderingSession** ret) override { START_COM_CALL; ComPtr releaseContext; @synchronized (_target->lock) { if(_target->surface == nil) return E_FAIL; _target->_glContext->MakeCurrent(releaseContext.getPPV()); HRESULT res = [_target->surface prepareForGlRender]; if(res) return res; *ret = new AvnGlRenderingSession(_target, releaseContext); return S_OK; } } }; static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* target) { return new AvnGlRenderTarget(target); }