added GrabbableCamera

This commit is contained in:
shr 2023-09-24 03:59:24 +09:00 committed by Cameron Taylor
parent 9fc3a46b48
commit ab7ba485cb
9 changed files with 306 additions and 30 deletions

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}
/**

View file

@ -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
{

View file

@ -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<BitmapData> = [];
final texturePool:Array<TextureBase> = [];
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();
}
}

View file

@ -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;

View file

@ -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.

View file

@ -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,8 +697,11 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
debugIconGroup = null;
}
if (frameBufferMan != null)
{
frameBufferMan.dispose();
}
}
/**
* A function that gets called once per step in the song.
@ -750,17 +754,51 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass
}
override function draw():Void
{
if (frameBufferMan != null)
{
frameBufferMan.lock();
}
super.draw();
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) {}

View file

@ -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