From ab7ba485cbf81bf1b24628ebae3056e6c3588713 Mon Sep 17 00:00:00 2001 From: shr Date: Sun, 24 Sep 2023 03:59:24 +0900 Subject: [PATCH] added GrabbableCamera --- .../graphics/framebuffer/BitmapDataTools.hx | 43 +++++ .../graphics/framebuffer/FixedBitmapData.hx | 42 +++++ .../graphics/framebuffer/FrameBuffer.hx | 13 +- .../framebuffer/FrameBufferManager.hx | 7 +- .../graphics/framebuffer/GrabbableCamera.hx | 161 ++++++++++++++++++ .../graphics/shaders/RuntimeRainShader.hx | 17 +- source/funkin/play/PlayState.hx | 2 +- source/funkin/play/stage/Stage.hx | 48 +++++- source/funkin/ui/SwagCamera.hx | 3 +- 9 files changed, 306 insertions(+), 30 deletions(-) create mode 100644 source/funkin/graphics/framebuffer/BitmapDataTools.hx create mode 100644 source/funkin/graphics/framebuffer/FixedBitmapData.hx create mode 100644 source/funkin/graphics/framebuffer/GrabbableCamera.hx diff --git a/source/funkin/graphics/framebuffer/BitmapDataTools.hx b/source/funkin/graphics/framebuffer/BitmapDataTools.hx new file mode 100644 index 000000000..9743d7e0b --- /dev/null +++ b/source/funkin/graphics/framebuffer/BitmapDataTools.hx @@ -0,0 +1,43 @@ +package funkin.graphics.framebuffer; + +import flixel.FlxG; +import openfl.display.Bitmap; +import openfl.display.BitmapData; +import openfl.display.Sprite; +import openfl.filters.BitmapFilter; + +/** + * Provides cool stuff for `BitmapData`s that have a hardware texture internally. + */ +class BitmapDataTools +{ + /** + * Applies a bitmap filter to a bitmap immediately. The bitmap filter may refer + * the bitmap itself as a shader input. + * @param bitmap the bitmap data + * @param filter the bitmap filter + */ + public static function applyFilter(bitmap:BitmapData, filter:BitmapFilter):Void + { + if (bitmap.readable) + { + FlxG.log.error('do not use `BitmapDataTools` for non-GPU bitmaps!'); + } + // man, allow me to use anon structuers for local vars! + static var cache:{sprite:Sprite, bitmap:Bitmap} = null; + if (cache == null) + { + final sprite = new Sprite(); + final bitmap = new Bitmap(); + sprite.addChild(bitmap); + cache = + { + sprite: sprite, + bitmap: bitmap + } + } + cache.bitmap.bitmapData = bitmap; + cache.sprite.filters = [filter]; + bitmap.draw(cache.sprite); + } +} diff --git a/source/funkin/graphics/framebuffer/FixedBitmapData.hx b/source/funkin/graphics/framebuffer/FixedBitmapData.hx new file mode 100644 index 000000000..00b39ce1c --- /dev/null +++ b/source/funkin/graphics/framebuffer/FixedBitmapData.hx @@ -0,0 +1,42 @@ +package funkin.graphics.framebuffer; + +import openfl.display.BitmapData; +import openfl.display.DisplayObject; +import openfl.display.DisplayObjectContainer; +import openfl.display.IBitmapDrawable; +import openfl.display.OpenGLRenderer; +import openfl.display3D.textures.TextureBase; + +/** + * `BitmapData` is kinda broken so I fixed it. + */ +@:access(openfl.display3D.textures.TextureBase) +@:access(openfl.display.OpenGLRenderer) +class FixedBitmapData extends BitmapData +{ + override function __drawGL(source:IBitmapDrawable, renderer:OpenGLRenderer):Void + { + if (Std.isOfType(source, DisplayObject)) + { + final object:DisplayObjectContainer = cast source; + renderer.__stage = object.stage; + } + super.__drawGL(source, renderer); + } + + /** + * Never use `BitmapData.fromTexture`, always use this. + * @param texture the texture + * @return the bitmap data + */ + public static function fromTexture(texture:TextureBase):FixedBitmapData + { + if (texture == null) return null; + final bitmapData = new FixedBitmapData(texture.__width, texture.__height, true, 0); + bitmapData.readable = false; + bitmapData.__texture = texture; + bitmapData.__textureContext = texture.__textureContext; + bitmapData.image = null; + return bitmapData; + } +} diff --git a/source/funkin/graphics/framebuffer/FrameBuffer.hx b/source/funkin/graphics/framebuffer/FrameBuffer.hx index 0209a4aef..786f980ea 100644 --- a/source/funkin/graphics/framebuffer/FrameBuffer.hx +++ b/source/funkin/graphics/framebuffer/FrameBuffer.hx @@ -1,12 +1,12 @@ package funkin.graphics.framebuffer; -import openfl.geom.Rectangle; -import openfl.geom.Matrix; -import openfl.display.BitmapData; -import flixel.util.FlxColor; import flixel.FlxCamera; +import flixel.util.FlxColor; import openfl.Lib; +import openfl.display.BitmapData; import openfl.display3D.textures.TextureBase; +import openfl.geom.Matrix; +import openfl.geom.Rectangle; /** * A single frame buffer. Used by `FrameBufferManager`. @@ -27,7 +27,6 @@ class FrameBuffer camera = new FlxCamera(); camera.antialiasing = false; camera.bgColor = FlxColor.TRANSPARENT; - camera.flashSprite.cacheAsBitmap = true; @:privateAccess camera.flashSprite.stage = Lib.current.stage; } @@ -41,7 +40,7 @@ class FrameBuffer { dispose(); texture = Lib.current.stage.context3D.createTexture(width, height, BGRA, true); - bitmap = BitmapData.fromTexture(texture); + bitmap = FixedBitmapData.fromTexture(texture); camera.bgColor = bgColor; } @@ -108,7 +107,7 @@ class FrameBuffer bitmap.dispose(); bitmap = null; } - spriteCopies.clear(); + spriteCopies.resize(0); } /** diff --git a/source/funkin/graphics/framebuffer/FrameBufferManager.hx b/source/funkin/graphics/framebuffer/FrameBufferManager.hx index 6cfadd468..5ed4afb3a 100644 --- a/source/funkin/graphics/framebuffer/FrameBufferManager.hx +++ b/source/funkin/graphics/framebuffer/FrameBufferManager.hx @@ -1,9 +1,10 @@ package funkin.graphics.framebuffer; +import flixel.FlxCamera; +import flixel.FlxG; +import flixel.FlxSprite; import flixel.util.FlxColor; import openfl.display.BitmapData; -import flixel.FlxSprite; -import flixel.FlxCamera; /** * Manages frame buffers and gives access to each frame buffer. @@ -56,7 +57,7 @@ class FrameBufferManager } /** - * Call this before everything is drawn. + * Call this before drawing anything. */ public function lock():Void { diff --git a/source/funkin/graphics/framebuffer/GrabbableCamera.hx b/source/funkin/graphics/framebuffer/GrabbableCamera.hx new file mode 100644 index 000000000..34fb8c9ca --- /dev/null +++ b/source/funkin/graphics/framebuffer/GrabbableCamera.hx @@ -0,0 +1,161 @@ +package funkin.graphics.framebuffer; + +import flixel.FlxCamera; +import flixel.FlxG; +import flixel.graphics.FlxGraphic; +import flixel.graphics.frames.FlxFrame; +import flixel.math.FlxMatrix; +import flixel.math.FlxRect; +import flixel.system.FlxAssets.FlxShader; +import openfl.Lib; +import openfl.display.BitmapData; +import openfl.display3D.textures.TextureBase; + +/** + * A camera, but grabbable. + */ +@:access(openfl.display.DisplayObject) +@:access(openfl.display.BitmapData) +@:access(openfl.display3D.Context3D) +@:access(openfl.display3D.textures.TextureBase) +@:access(flixel.graphics.FlxGraphic) +@:access(flixel.graphics.frames.FlxFrame) +class GrabbableCamera extends FlxCamera +{ + final grabbed:Array = []; + final texturePool:Array = []; + final defaultShader:FlxShader = new FlxShader(); + + final bgTexture:TextureBase; + final bgBitmap:BitmapData; + final bgFrame:FlxFrame; + + public function new(x:Int = 0, y:Int = 0, width:Int = 0, height:Int = 0, zoom:Float = 0) + { + super(x, y, width, height, zoom); + bgTexture = pickTexture(width, height); + bgBitmap = FixedBitmapData.fromTexture(bgTexture); + bgFrame = new FlxFrame(new FlxGraphic('', null)); + bgFrame.parent.bitmap = bgBitmap; + bgFrame.frame = new FlxRect(); + } + + /** + * Grabs the camera screen and returns it as a `BitmapData`. The returned bitmap + * will not be referred by the camera, so changing it will not affect the scene. + * @param applyFilters if this is `true`, the camera's filters will be applied to the grabbed bitmap + * @return the grabbed bitmap data + */ + public function grabScreen(applyFilters:Bool):BitmapData + { + final texture = pickTexture(width, height); + final bitmap = FixedBitmapData.fromTexture(texture); + squashTo(bitmap, applyFilters); + return bitmap; + } + + function squashTo(bitmap:BitmapData, applyFilters:Bool):Void + { + static final matrix = new FlxMatrix(); + + // resize the background bitmap if needed + if (bgTexture.__width != width || bgTexture.__height != height) + { + resizeTexture(bgTexture, width, height); + bgBitmap.__resize(width, height); + bgFrame.parent.bitmap = bgBitmap; + } + + // grab the bitmap + render(); + bitmap.fillRect(bitmap.rect, 0); + matrix.setTo(1, 0, 0, 1, flashSprite.x, flashSprite.y); + if (applyFilters) + { + bitmap.draw(flashSprite, matrix); + } + else + { + final tmp = flashSprite.filters; + flashSprite.filters = null; + bitmap.draw(flashSprite, matrix); + flashSprite.filters = tmp; + } + + // also copy to the background bitmap + bgBitmap.fillRect(bgBitmap.rect, 0); + bgBitmap.draw(bitmap); + + // clear graphics data + super.clearDrawStack(); + canvas.graphics.clear(); + + // render the background bitmap + bgFrame.frame.set(0, 0, width, height); + matrix.setTo(viewWidth / width, 0, 0, viewHeight / height, viewMarginLeft, viewMarginTop); + drawPixels(bgFrame, matrix); + } + + function resizeTexture(texture:TextureBase, width:Int, height:Int):Void + { + texture.__width = width; + texture.__height = height; + final context = texture.__context; + final gl = context.gl; + context.__bindGLTexture2D(texture.__textureID); + gl.texImage2D(gl.TEXTURE_2D, 0, texture.__internalFormat, width, height, 0, texture.__format, gl.UNSIGNED_BYTE, null); + context.__bindGLTexture2D(null); + } + + override function destroy():Void + { + super.destroy(); + disposeTextures(); + } + + override function clearDrawStack():Void + { + super.clearDrawStack(); + // also clear grabbed bitmaps + for (bitmap in grabbed) + { + texturePool.push(@:privateAccess bitmap.__texture); + bitmap.dispose(); + } + grabbed.clear(); + } + + function pickTexture(width:Int, height:Int):TextureBase + { + // zero-sized textures will be problematic + width = width < 1 ? 1 : width; + height = height < 1 ? 1 : height; + if (texturePool.length > 0) + { + final res = texturePool.pop(); + if (res.__width != width || res.__height != height) + { + resizeTexture(res, width, height); + } + return res; + } + return Lib.current.stage.context3D.createTexture(width, height, BGRA, true); + } + + function disposeTextures():Void + { + trace('disposing textures'); + for (bitmap in grabbed) + { + bitmap.dispose(); + } + grabbed.clear(); + for (texture in texturePool) + { + texture.dispose(); + } + texturePool.resize(0); + bgTexture.dispose(); + bgBitmap.dispose(); + } +} diff --git a/source/funkin/graphics/shaders/RuntimeRainShader.hx b/source/funkin/graphics/shaders/RuntimeRainShader.hx index 7889e2a33..77608b98e 100644 --- a/source/funkin/graphics/shaders/RuntimeRainShader.hx +++ b/source/funkin/graphics/shaders/RuntimeRainShader.hx @@ -61,21 +61,12 @@ class RuntimeRainShader extends RuntimePostEffectShader return puddleY = value; } - public var groundMap(default, set):BitmapData; + public var blurredScreen(default, set):BitmapData; - function set_groundMap(value:BitmapData):BitmapData + function set_blurredScreen(value:BitmapData):BitmapData { - this.setBitmapData('uGroundMap', value); - // this.setFloat2('uPuddleTextureSize', value.width, value.height); - return groundMap = value; - } - - public var lightMap(default, set):BitmapData; - - function set_lightMap(value:BitmapData):BitmapData - { - this.setBitmapData('uLightMap', value); - return lightMap = value; + this.setBitmapData('uBlurredScreen', value); + return blurredScreen = value; } public var mask(default, set):BitmapData; diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 75a311a45..9fa6929d9 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -411,7 +411,7 @@ class PlayState extends MusicBeatSubState /** * The camera which contains, and controls visibility of, the stage and characters. */ - public var camGame:FlxCamera; + public var camGame:SwagCamera; /** * The camera which contains, and controls visibility of, a video cutscene. diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx index ada8c898d..def67bdbc 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -3,6 +3,7 @@ package funkin.play.stage; import funkin.graphics.framebuffer.FrameBufferManager; import flixel.util.FlxColor; import funkin.graphics.framebuffer.SpriteCopy; +import funkin.graphics.framebuffer.GrabbableCamera; import flixel.FlxCamera; import flixel.FlxSprite; import flixel.group.FlxSpriteGroup; @@ -78,7 +79,7 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass { if (frameBufferMan != null) frameBufferMan.dispose(); frameBufferMan = new FrameBufferManager(FlxG.camera); - onFrameBufferCreate(); + setupFrameBuffers(); buildStage(); this.refresh(); @@ -696,7 +697,10 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass debugIconGroup = null; } - frameBufferMan.dispose(); + if (frameBufferMan != null) + { + frameBufferMan.dispose(); + } } /** @@ -751,16 +755,50 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass override function draw():Void { - frameBufferMan.lock(); + if (frameBufferMan != null) + { + frameBufferMan.lock(); + } super.draw(); - frameBufferMan.unlock(); + if (frameBufferMan != null) + { + frameBufferMan.unlock(); + } + frameBuffersUpdated(); } /** * Called when the frame buffer manager is ready. * Create frame buffers inside this method. */ - public function onFrameBufferCreate():Void {} + function setupFrameBuffers():Void {} + + /** + * Called when all the frame buffers are updated. If you need any + * frame buffers before `grabScreen()`, make sure you + * grab the screen inside this method since it immediately uses the + * frame buffers. + */ + function frameBuffersUpdated():Void {} + + /** + * Grabs the current screen and returns it as a bitmap data. You can sefely modify it. + * @param applyFilters if this is `true`, the filters set to the camera will be applied to the resulting bitmap + * @return the grabbed screen + */ + function grabScreen(applyFilters:Bool):BitmapData + { + if (Std.isOfType(FlxG.camera, GrabbableCamera)) + { + final cam:GrabbableCamera = cast FlxG.camera; + return cam.grabScreen(applyFilters); + } + else + { + FlxG.log.error('cannot grab the screen: the main camera is not grabbable'); + return null; + } + } public function onScriptEvent(event:ScriptEvent) {} diff --git a/source/funkin/ui/SwagCamera.hx b/source/funkin/ui/SwagCamera.hx index 70791d38f..bb6a24c3d 100644 --- a/source/funkin/ui/SwagCamera.hx +++ b/source/funkin/ui/SwagCamera.hx @@ -1,11 +1,12 @@ package funkin.ui; +import funkin.graphics.framebuffer.GrabbableCamera; import flixel.FlxCamera; import flixel.FlxSprite; import flixel.math.FlxPoint; import funkin.util.MathUtil; -class SwagCamera extends FlxCamera +class SwagCamera extends GrabbableCamera { /** * properly follow framerate