diff --git a/ares/ares/node/video/screen.cpp b/ares/ares/node/video/screen.cpp index 2fe73a2de5..de83af34d7 100644 --- a/ares/ares/node/video/screen.cpp +++ b/ares/ares/node/video/screen.cpp @@ -104,6 +104,7 @@ auto Screen::setScale(f64 scaleX, f64 scaleY) -> void { lock_guard lock(_mutex); _scaleX = scaleX; _scaleY = scaleY; + platform->setScale(scaleX, scaleY); } auto Screen::setAspect(f64 aspectX, f64 aspectY) -> void { @@ -160,6 +161,7 @@ auto Screen::setProgressive(bool progressiveDouble) -> void { _interlace = false; _progressive = true; _progressiveDouble = progressiveDouble; + platform->setProgressive(progressiveDouble); } auto Screen::setInterlace(bool interlaceField) -> void { @@ -167,6 +169,7 @@ auto Screen::setInterlace(bool interlaceField) -> void { _progressive = false; _interlace = true; _interlaceField = interlaceField; + platform->setInterlace(interlaceField); } auto Screen::attach(Node::Video::Sprite sprite) -> void { diff --git a/ares/ares/platform.hpp b/ares/ares/platform.hpp index 527a105831..c21624e6b8 100644 --- a/ares/ares/platform.hpp +++ b/ares/ares/platform.hpp @@ -19,6 +19,9 @@ struct Platform { virtual auto status(string_view message) -> void {} virtual auto video(Node::Video::Screen, const u32* data, u32 pitch, u32 width, u32 height) -> void {} virtual auto refreshRateHint(double refreshRate) -> void {} + virtual auto setScale(f64 scaleX, f64 scaleY) -> void {} + virtual auto setInterlace(bool interlaceField) -> void {} + virtual auto setProgressive(bool progressivedouble) -> void {} virtual auto audio(Node::Audio::Stream) -> void {} virtual auto input(Node::Input::Input) -> void {} virtual auto cheat(u32 addr) -> maybe { return nothing; } diff --git a/desktop-ui/program/platform.cpp b/desktop-ui/program/platform.cpp index 06e7db8d28..cb2a27a29d 100644 --- a/desktop-ui/program/platform.cpp +++ b/desktop-ui/program/platform.cpp @@ -136,6 +136,18 @@ auto Program::refreshRateHint(double refreshRate) -> void { ruby::video.refreshRateHint(refreshRate); } +auto Program::setScale(f64 scaleX, f64 scaleY) -> void { + ruby::video.setScale(scaleX, scaleY); +} + +auto Program::setInterlace(bool interlaceField) -> void { + ruby::video.setInterlace(interlaceField); +} + +auto Program::setProgressive(bool progressiveDouble) -> void { + ruby::video.setProgressive(progressiveDouble); +} + auto Program::audio(ares::Node::Audio::Stream node) -> void { if(!streams) return; diff --git a/desktop-ui/program/program.hpp b/desktop-ui/program/program.hpp index 98280fdf7e..7e71a135ce 100644 --- a/desktop-ui/program/program.hpp +++ b/desktop-ui/program/program.hpp @@ -12,6 +12,9 @@ struct Program : ares::Platform { auto status(string_view message) -> void override; auto video(ares::Node::Video::Screen, const u32* data, u32 pitch, u32 width, u32 height) -> void override; auto refreshRateHint(double refreshRate) -> void override; + auto setScale(f64 scaleX, f64 scaleY) -> void override; + auto setInterlace(bool interlaceField) -> void override; + auto setProgressive(bool progressiveDouble) -> void override; auto audio(ares::Node::Audio::Stream) -> void override; auto input(ares::Node::Input::Input) -> void override; auto cheat(u32 address) -> maybe override; diff --git a/ruby/video/metal/metal.cpp b/ruby/video/metal/metal.cpp index a264ce9420..023c5f4995 100644 --- a/ruby/video/metal/metal.cpp +++ b/ruby/video/metal/metal.cpp @@ -210,6 +210,70 @@ struct VideoMetal : VideoDriver, Metal { _outputX = (width - outputWidth) / 2; _outputY = (height - outputHeight) / 2; } + + auto resizeSourceBuffers() { + for (int i = 0; i < kMaxSourceBuffersInFlight; i++) { + if (sourceWidth < 1 || sourceHeight < 1) { + _sourceTextures[i] = nullptr; + continue; + } + MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor new]; + textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm; + textureDescriptor.width = sourceWidth; + textureDescriptor.height = sourceHeight; + textureDescriptor.usage = MTLTextureUsageRenderTarget|MTLTextureUsageShaderRead; + + _sourceTextures[i] = [_device newTextureWithDescriptor:textureDescriptor]; + } + + f64 newWidth = sourceWidth * _scaleX; + f64 newHeight = sourceHeight * _scaleY; + if (_interlace) { + newHeight = sourceHeight; + } + for (int i = 0; i < kMaxSourceBuffersInFlight; i++) { + if (newWidth < 1 || newHeight < 1) { + _finalSourceTextures[i] = nullptr; + continue; + } + MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor new]; + textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm; + textureDescriptor.width = newWidth; + textureDescriptor.height = newHeight; + textureDescriptor.usage = MTLTextureUsageRenderTarget|MTLTextureUsageShaderRead; + + _finalSourceTextures[i] = [_device newTextureWithDescriptor:textureDescriptor]; + } + NSLog(@"Resized final source textures to %lf, %lf", newWidth, newHeight); + } + + auto setScale(f64 scaleX, f64 scaleY) -> void override { + if (scaleX != _scaleX || scaleY != scaleY) { + _scaleX = scaleX; + _scaleY = scaleY; + dispatch_async(_renderQueue, ^{ + resizeSourceBuffers(); + }); + } + } + + auto setInterlace(bool interlaceField) -> void override { + if (_interlace) return; + _interlace = true; + _progressive = false; + dispatch_async(_renderQueue, ^{ + resizeSourceBuffers(); + }); + } + + auto setProgressive(bool progressiveDouble) -> void override { + if (_progressive) return; + _interlace = false; + _progressive = true; + dispatch_async(_renderQueue, ^{ + resizeSourceBuffers(); + }); + } auto acquire(u32*& data, u32& pitch, u32 width, u32 height) -> bool override { if (sourceWidth != width || sourceHeight != height) { @@ -225,21 +289,10 @@ struct VideoMetal : VideoDriver, Metal { bytesPerRow = sourceWidth * sizeof(u32); if (bytesPerRow < 16) bytesPerRow = 16; - - for (int i = 0; i < kMaxSourceBuffersInFlight; i++) { - if (sourceWidth < 1 || sourceHeight < 1) { - _sourceTextures[i] = nullptr; - continue; - } - MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor new]; - textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm; - textureDescriptor.width = sourceWidth; - textureDescriptor.height = sourceHeight; - textureDescriptor.usage = MTLTextureUsageRenderTarget|MTLTextureUsageShaderRead; - - _sourceTextures[i] = [_device newTextureWithDescriptor:textureDescriptor]; - } + dispatch_async(_renderQueue, ^{ + resizeSourceBuffers(); + }); } pitch = sourceWidth * sizeof(u32); return data = buffer; @@ -312,6 +365,7 @@ struct VideoMetal : VideoDriver, Metal { auto index = frameCount % kMaxSourceBuffersInFlight; auto sourceTexture = _sourceTextures[index]; + auto finalSourceTexture = _finalSourceTextures[index]; [sourceTexture replaceRegion:MTLRegionMake2D(0, 0, sourceWidth, sourceHeight) mipmapLevel:0 withBytes:buffer bytesPerRow:bytesPerRow]; @@ -325,18 +379,18 @@ struct VideoMetal : VideoDriver, Metal { /// assurances that we won't block the emulation thread in the worst case system conditions. if ((_blocking && !_vrrIsSupported) || !_threaded) { dispatch_sync(_renderQueue, ^{ - outputHelper(width, height, sourceTexture); + outputHelper(width, height, sourceTexture, finalSourceTexture); }); } else { dispatch_async(_renderQueue, ^{ - outputHelper(width, height, sourceTexture); + outputHelper(width, height, sourceTexture, finalSourceTexture); }); } } } private: - auto outputHelper(u32 width, u32 height, id sourceTexture) -> void { + auto outputHelper(u32 width, u32 height, id sourceTexture, id finalSourceTexture) -> void { /// Uses two render passes (plus librashader's render passes). The first render pass samples the source texture, /// consisting of the pixel buffer from the emulator, onto a texture the same size as our eventual output, /// `_renderTargetTexture`. Then it calls into librashader, which performs postprocessing onto the same @@ -355,7 +409,7 @@ struct VideoMetal : VideoDriver, Metal { dispatch_semaphore_signal(block_sema); }]; - _renderToTextureRenderPassDescriptor.colorAttachments[0].texture = _renderTargetTexture; + _renderToTextureRenderPassDescriptor.colorAttachments[0].texture = finalSourceTexture; if (_renderToTextureRenderPassDescriptor != nil) { @@ -365,7 +419,7 @@ struct VideoMetal : VideoDriver, Metal { [renderEncoder setRenderPipelineState:_renderToTextureRenderPipeline]; - [renderEncoder setViewport:(MTLViewport){0, 0, (double)width, (double)height, -1.0, 1.0}]; + [renderEncoder setViewport:(MTLViewport){0, 0, (double)finalSourceTexture.width, (double)finalSourceTexture.height, -1.0, 1.0}]; [renderEncoder setVertexBuffer:_vertexBuffer offset:0 @@ -382,7 +436,36 @@ struct VideoMetal : VideoDriver, Metal { [renderEncoder endEncoding]; if (_filterChain) { - _libra.mtl_filter_chain_frame(&_filterChain, commandBuffer, frameCount, sourceTexture, _libraViewport, _renderTargetTexture, nil, nil); + _libra.mtl_filter_chain_frame(&_filterChain, commandBuffer, frameCount, finalSourceTexture, _libraViewport, _renderTargetTexture, nil, nil); + } else { + + _renderToTextureRenderPassDescriptor.colorAttachments[0].texture = _renderTargetTexture; + + if (_renderToTextureRenderPassDescriptor != nil) { + + id renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:_renderToTextureRenderPassDescriptor]; + + _renderToTextureRenderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore; + + [renderEncoder setRenderPipelineState:_renderToTextureRenderPipeline]; + + [renderEncoder setViewport:(MTLViewport){0, 0, (double)width, (double)height, -1.0, 1.0}]; + + [renderEncoder setVertexBuffer:_vertexBuffer + offset:0 + atIndex:0]; + + [renderEncoder setVertexBytes:&_viewportSize + length:sizeof(_viewportSize) + atIndex:MetalVertexInputIndexViewportSize]; + + [renderEncoder setFragmentTexture:finalSourceTexture atIndex:0]; + + [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:6]; + + [renderEncoder endEncoding]; + + } } //this call will block the current thread/queue if a drawable is not yet available diff --git a/ruby/video/metal/metal.hpp b/ruby/video/metal/metal.hpp index 37d46e4552..b3359ae49c 100644 --- a/ruby/video/metal/metal.hpp +++ b/ruby/video/metal/metal.hpp @@ -45,6 +45,10 @@ struct Metal { auto initialize(const string& shader) -> bool; auto terminate() -> void; auto refreshRateHint(double refreshRate) -> void; + auto setScale(f64 scaleX, f64 scaleY) -> void; + auto setInterlace(bool interlaceField) -> void; + auto setProgressive(bool progressiveDouble) -> void; + auto resizeSourceBuffers() -> void; auto size(u32 width, u32 height) -> void; auto release() -> void; @@ -54,6 +58,8 @@ struct Metal { u32 sourceWidth = 0; u32 sourceHeight = 0; + f64 _scaleX = 1; + f64 _scaleY = 1; u32 bytesPerRow = 0; u32 outputWidth = 0; @@ -62,6 +68,9 @@ struct Metal { double _outputY = 0; u32 depth = 0; + bool _interlace = false; + bool _progressive = false; + dispatch_queue_t _renderQueue = nullptr; CGFloat _viewWidth = 0; @@ -89,6 +98,7 @@ struct Metal { id _vertexBuffer; id _sourceTextures[kMaxSourceBuffersInFlight]; + id _finalSourceTextures[kMaxSourceBuffersInFlight]; MTLVertexDescriptor *_mtlVertexDescriptor; MTLRenderPassDescriptor *_renderToTextureRenderPassDescriptor; diff --git a/ruby/video/video.cpp b/ruby/video/video.cpp index 2c24fdde81..eaa54229b4 100644 --- a/ruby/video/video.cpp +++ b/ruby/video/video.cpp @@ -113,6 +113,21 @@ auto Video::refreshRateHint(double refreshRate) -> void { instance->refreshRateHint(refreshRate); } +auto Video::setScale(f64 scaleX, f64 scaleY) -> void { + lock_guard lock(mutex); + instance->setScale(scaleX, scaleY); +} + +auto Video::setInterlace(bool interlaceField) -> void { + lock_guard lock(mutex); + instance->setInterlace(interlaceField); +} + +auto Video::setProgressive(bool progressiveDouble) -> void { + lock_guard lock(mutex); + instance->setProgressive(progressiveDouble); +} + // auto Video::focused() -> bool { diff --git a/ruby/video/video.hpp b/ruby/video/video.hpp index 73e9228838..607b581019 100644 --- a/ruby/video/video.hpp +++ b/ruby/video/video.hpp @@ -34,6 +34,9 @@ struct VideoDriver { virtual auto setFormat(string format) -> bool { return true; } virtual auto setShader(string shader) -> bool { return true; } virtual auto refreshRateHint(double refreshRate) -> void {} + virtual auto setScale(f64 scaleX, f64 scaleY) -> void {} + virtual auto setInterlace(bool interlaceField) -> void {} + virtual auto setProgressive(bool progressiveDouble) -> void {} virtual auto focused() -> bool { return true; } virtual auto clear() -> void {} @@ -129,6 +132,9 @@ struct Video { auto setFormat(string format) -> bool; auto setShader(string shader) -> bool; auto refreshRateHint(double refreshRate) -> void; + auto setScale(f64 scaleX, f64 scaleY) -> void; + auto setInterlace(bool interlaceField) -> void; + auto setProgressive(bool progressiveDouble) -> void; auto focused() -> bool; auto clear() -> void;