From 1a5c76ddcc3b0c2f185c59c2f8b238fb5a7bc321 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Fri, 30 Jan 2026 16:17:53 +0800 Subject: [PATCH 01/13] Create BloomEffect.hx --- source/funkin/backend/shaders/BloomEffect.hx | 486 +++++++++++++++++++ 1 file changed, 486 insertions(+) create mode 100644 source/funkin/backend/shaders/BloomEffect.hx diff --git a/source/funkin/backend/shaders/BloomEffect.hx b/source/funkin/backend/shaders/BloomEffect.hx new file mode 100644 index 000000000..8084e46bd --- /dev/null +++ b/source/funkin/backend/shaders/BloomEffect.hx @@ -0,0 +1,486 @@ +package funkin.backend.shaders; + +import haxe.Timer; +#if !flash +import openfl.filters.BitmapFilter; +import openfl.filters.BitmapFilterShader; +import openfl.display.BitmapData; +import openfl.display.DisplayObjectRenderer; +import openfl.display.Shader; +import openfl.geom.Point; +import openfl.geom.Rectangle; +#if lime +import lime._internal.graphics.ImageDataUtil; // TODO + +#end + +/** + The BlurFilter class lets you apply a blur visual effect to display + objects. A blur effect softens the details of an image. You can produce + blurs that range from a softly unfocused look to a Gaussian blur, a hazy + appearance like viewing an image through semi-opaque glass. When the + `quality` property of this filter is set to low, the result is a + softly unfocused look. When the `quality` property is set to + high, it approximates a Gaussian blur filter. You can apply the filter to + any display object(that is, objects that inherit from the DisplayObject + class), such as MovieClip, SimpleButton, TextField, and Video objects, as + well as to BitmapData objects. + + To create a new filter, use the constructor `new + BlurFilter()`. The use of filters depends on the object to which you + apply the filter: + + + * To apply filters to movie clips, text fields, buttons, and video, use + the `filters` property(inherited from DisplayObject). Setting + the `filters` property of an object does not modify the object, + and you can remove the filter by clearing the `filters` + property. + * To apply filters to BitmapData objects, use the + `BitmapData.applyFilter()` method. Calling + `applyFilter()` on a BitmapData object takes the source + BitmapData object and the filter object and generates a filtered image as a + result. + + + If you apply a filter to a display object, the + `cacheAsBitmap` property of the display object is set to + `true`. If you remove all filters, the original value of + `cacheAsBitmap` is restored. + + This filter supports Stage scaling. However, it does not support general + scaling, rotation, and skewing. If the object itself is scaled + (`scaleX` and `scaleY` are not set to 100%), the + filter effect is not scaled. It is scaled only when the user zooms in on + the Stage. + + A filter is not applied if the resulting image exceeds the maximum + dimensions. In AIR 1.5 and Flash Player 10, the maximum is 8,191 pixels in + width or height, and the total number of pixels cannot exceed 16,777,215 + pixels.(So, if an image is 8,191 pixels wide, it can only be 2,048 pixels + high.) In Flash Player 9 and earlier and AIR 1.1 and earlier, the + limitation is 2,880 pixels in height and 2,880 pixels in width. If, for + example, you zoom in on a large movie clip with a filter applied, the + filter is turned off if the resulting image exceeds the maximum + dimensions. +**/ +#if !openfl_debug +@:fileXml('tags="haxe,release"') +@:noDebug +#end +@:access(openfl.geom.Point) +@:access(openfl.geom.Rectangle) +@:final class BloomEffect extends BitmapFilter +{ + @:noCompletion private static var __blurShader:BlurShader = new BlurShader(); + @:noCompletion private static var __combineShader:CombineShader = new CombineShader(); + @:noCompletion private static var __extractShader:ExtractShader = new ExtractShader(); + + /** + The amount of horizontal blur. Valid values are from 0 to 255(floating + point). The default value is 4. Values that are a power of 2(such as 2, + 4, 8, 16 and 32) are optimized to render more quickly than other values. + **/ + public var blurX(get, set):Float; + + /** + The amount of vertical blur. Valid values are from 0 to 255(floating + point). The default value is 4. Values that are a power of 2(such as 2, + 4, 8, 16 and 32) are optimized to render more quickly than other values. + **/ + public var blurY(get, set):Float; + + /** + The number of times to perform the blur. The default value is + `BitmapFilterQuality.LOW`, which is equivalent to applying the + filter once. The value `BitmapFilterQuality.MEDIUM` applies the + filter twice; the value `BitmapFilterQuality.HIGH` applies it + three times and approximates a Gaussian blur. Filters with lower values + are rendered more quickly. + + For most applications, a `quality` value of low, medium, or + high is sufficient. Although you can use additional numeric values up to + 15 to increase the number of times the blur is applied, higher values are + rendered more slowly. Instead of increasing the value of + `quality`, you can often get a similar effect, and with faster + rendering, by simply increasing the values of the `blurX` and + `blurY` properties. + + You can use the following BitmapFilterQuality constants to specify + values of the `quality` property: + + * `BitmapFilterQuality.LOW` + * `BitmapFilterQuality.MEDIUM` + * `BitmapFilterQuality.HIGH` + **/ + public var quality(get, set):Float; + + public var strength(get, set):Float; + + public var threshold(get, set):Float; + + @:noCompletion private var __blurX:Float; + @:noCompletion private var __blurY:Float; + @:noCompletion private var __horizontalPasses:Int; + @:noCompletion private var __quality:Float; + @:noCompletion private var __verticalPasses:Int; + @:noCompletion private var __strength:Float; + @:noCompletion private var __threshold:Float; + + #if openfljs + @:noCompletion private static function __init__() + { + untyped Object.defineProperties(BloomEffect.prototype, { + "blurX": { + get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_blurX (); }"), + set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_blurX (v); }") + }, + "blurY": { + get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_blurY (); }"), + set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_blurY (v); }") + }, + "quality": { + get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_quality (); }"), + set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_quality (v); }") + }, + "strength": { + get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_strength (); }"), + set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_strength (v); }") + }, + "threshold": { + get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_threshold (); }"), + set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_threshold (v); }") + }, + }); + } + #end + + /** + Initializes the filter with the specified parameters. The default values + create a soft, unfocused image. + + @param blurX The amount to blur horizontally. Valid values are from 0 to + 255.0(floating-point value). + @param blurY The amount to blur vertically. Valid values are from 0 to + 255.0(floating-point value). + @param quality The number of times to apply the filter. You can specify + the quality using the BitmapFilterQuality constants: + + + * `openfl.filters.BitmapFilterQuality.LOW` + + * `openfl.filters.BitmapFilterQuality.MEDIUM` + + * `openfl.filters.BitmapFilterQuality.HIGH` + + + High quality approximates a Gaussian blur. For most + applications, these three values are sufficient. Although + you can use additional numeric values up to 15 to achieve + different effects, be aware that higher values are rendered + more slowly. + @param strength The intensity of the bloom effect. Default is 1.0. + @param threshold The brightness threshold for bloom. Pixels brighter than + this value will bloom. Default is 0.5. + **/ + public function new(blurX:Float = 4, blurY:Float = 4, quality:Float = 1, strength:Float = 1.0, threshold:Float = 0.5) + { + super(); + + this.blurX = blurX; + this.blurY = blurY; + this.quality = quality; + this.strength = strength; + this.threshold = threshold; + + __needSecondBitmapData = true; + __preserveObject = true; + __renderDirty = true; + } + + public override function clone():BitmapFilter + { + return new BloomEffect(__blurX, __blurY, __quality, __strength, __threshold); + } + + @:noCompletion private override function __applyFilter(bitmapData:BitmapData, sourceBitmapData:BitmapData, sourceRect:Rectangle, + destPoint:Point):BitmapData + { + #if lime + var time = Timer.stamp(); + var finalImage = ImageDataUtil.gaussianBlur(bitmapData.image, sourceBitmapData.image, sourceRect.__toLimeRectangle(), destPoint.__toLimeVector2(), + __blurX, __blurY, __quality); + var elapsed = Timer.stamp() - time; + // trace("blurX: " + __blurX + " blurY: " + __blurY + " quality: " + __quality + " elapsed: " + elapsed * 1000 + "ms"); + if (finalImage == bitmapData.image) return bitmapData; + #end + return sourceBitmapData; + } + + @:noCompletion private override function __initShader(renderer:DisplayObjectRenderer, pass:Int, sourceBitmapData:BitmapData):Shader + { + #if !macro + final numBlurPasses = __horizontalPasses + __verticalPasses; + + switch pass + { + case 0: + __extractShader.uThreshold.value = [__threshold]; + return __extractShader; + + case _ if (pass <= numBlurPasses): + final blurPass = pass - 1; + final isHorizontal = blurPass < __horizontalPasses; + + final scalePass = isHorizontal ? blurPass : blurPass - __horizontalPasses; + + final scale = Math.pow(0.5, scalePass >> 1); + final blurRadius = isHorizontal ? blurX * scale : blurY * scale; + + __blurShader.uRadius.value = isHorizontal ? [blurRadius, 0.0] : [0.0, blurRadius]; + + return __blurShader; + + default: + __combineShader.sourceBitmap.input = sourceBitmapData; + __combineShader.offset.value = [0.0, 0.0]; + __combineShader.uStrength.value = [__strength]; + __combineShader.uThreshold.value = [__threshold]; + return __combineShader; + } + #else + return null; + #end + } + + // Get & Set Methods + @:noCompletion private function get_blurX():Float + { + return __blurX; + } + + @:noCompletion private function set_blurX(value:Float):Float + { + if (value != __blurX) + { + __blurX = value; + __renderDirty = true; + __leftExtension = (value > 0 ? Math.ceil(value) : 0); + __rightExtension = __leftExtension; + } + return value; + } + + @:noCompletion private function get_blurY():Float + { + return __blurY; + } + + @:noCompletion private function set_blurY(value:Float):Float + { + if (value != __blurY) + { + __blurY = value; + __renderDirty = true; + __topExtension = (value > 0 ? Math.ceil(value) : 0); + __bottomExtension = __topExtension; + } + return value; + } + + @:noCompletion private function get_quality():Float + { + return __quality; + } + + @:noCompletion private function set_quality(value:Float):Float + { + // TODO: Quality effect with fewer passes? + + __horizontalPasses = (__blurX <= 0) ? 0 : Math.round(__blurX * (value / 4)) + 1; + __verticalPasses = (__blurY <= 0) ? 0 : Math.round(__blurY * (value / 4)) + 1; + + __numShaderPasses = __horizontalPasses + __verticalPasses + 2; + + if (value != __quality) __renderDirty = true; + return __quality = value; + } + + @:noCompletion private function get_strength():Float + { + return __strength; + } + + @:noCompletion private function set_strength(value:Float):Float + { + if (value != __strength) + { + __strength = value; + __renderDirty = true; + } + return value; + } + + @:noCompletion private function get_threshold():Float + { + return __threshold; + } + + @:noCompletion private function set_threshold(value:Float):Float + { + if (value != __threshold) + { + __threshold = value; + __renderDirty = true; + } + return value; + } +} + +#if !openfl_debug +@:fileXml('tags="haxe,release"') +@:noDebug +#end +private class BlurShader extends BitmapFilterShader +{ + @:glFragmentSource("uniform sampler2D openfl_Texture; + + varying vec2 vBlurCoords[7]; + + void main(void) { + vec4 sum = vec4(0.0); + sum += texture2D(openfl_Texture, vBlurCoords[0]) * 0.00443; + sum += texture2D(openfl_Texture, vBlurCoords[1]) * 0.05399; + sum += texture2D(openfl_Texture, vBlurCoords[2]) * 0.24197; + sum += texture2D(openfl_Texture, vBlurCoords[3]) * 0.39894; + sum += texture2D(openfl_Texture, vBlurCoords[4]) * 0.24197; + sum += texture2D(openfl_Texture, vBlurCoords[5]) * 0.05399; + sum += texture2D(openfl_Texture, vBlurCoords[6]) * 0.00443; + + gl_FragColor = sum; + }") + @:glVertexSource("attribute vec4 openfl_Position; + attribute vec2 openfl_TextureCoord; + + uniform mat4 openfl_Matrix; + + uniform vec2 uRadius; + varying vec2 vBlurCoords[7]; + uniform vec2 uTextureSize; + + void main(void) { + + gl_Position = openfl_Matrix * openfl_Position; + + vec2 r = uRadius / uTextureSize; + vBlurCoords[0] = openfl_TextureCoord - r; + vBlurCoords[1] = openfl_TextureCoord - r * 0.75; + vBlurCoords[2] = openfl_TextureCoord - r * 0.5; + vBlurCoords[3] = openfl_TextureCoord; + vBlurCoords[4] = openfl_TextureCoord + r * 0.5; + vBlurCoords[5] = openfl_TextureCoord + r * 0.75; + vBlurCoords[6] = openfl_TextureCoord + r; + + }") + public function new() + { + super(); + + #if !macro + uRadius.value = [0, 0]; + #end + } + + @:noCompletion private override function __update():Void + { + #if !macro + uTextureSize.value = [__texture.input.width, __texture.input.height]; + #end + + super.__update(); + } +} + +#if !openfl_debug +@:fileXml('tags="haxe,release"') +@:noDebug +#end +private class ExtractShader extends BitmapFilterShader +{ + @:glFragmentSource(" + uniform sampler2D openfl_Texture; + uniform float uThreshold; + varying vec2 vTexCoord; + + void main(void) { + vec4 texel = texture2D(openfl_Texture, vTexCoord); + float brightness = max(max(texel.r, texel.g), texel.b); + float mask = smoothstep(uThreshold, uThreshold + 0.1, brightness); + gl_FragColor = texel * mask; + } + ") + @:glVertexSource(" + attribute vec4 openfl_Position; + attribute vec2 openfl_TextureCoord; + uniform mat4 openfl_Matrix; + varying vec2 vTexCoord; + + void main(void) { + gl_Position = openfl_Matrix * openfl_Position; + vTexCoord = openfl_TextureCoord; + } + ") + public function new() + { + super(); + #if !macro + uThreshold.value = [0.5]; + #end + } +} + +#if !openfl_debug +@:fileXml('tags="haxe,release"') +@:noDebug +#end +private class CombineShader extends BitmapFilterShader +{ + @:glFragmentSource(" + uniform sampler2D openfl_Texture; + uniform sampler2D sourceBitmap; + uniform float uStrength; + uniform float uThreshold; + varying vec4 textureCoords; + + void main(void) { + vec4 src = texture2D(sourceBitmap, textureCoords.xy); + vec4 bloom = texture2D(openfl_Texture, textureCoords.zw); + + gl_FragColor = src + bloom * uStrength; + } + ") + @:glVertexSource("attribute vec4 openfl_Position; + attribute vec2 openfl_TextureCoord; + uniform mat4 openfl_Matrix; + uniform vec2 openfl_TextureSize; + uniform vec2 offset; + varying vec4 textureCoords; + + void main(void) { + gl_Position = openfl_Matrix * openfl_Position; + textureCoords = vec4(openfl_TextureCoord, openfl_TextureCoord - offset / openfl_TextureSize); + } + ") + public function new() + { + super(); + #if !macro + offset.value = [0, 0]; + uStrength.value = [1.0]; + uThreshold.value = [0.5]; + #end + } +} +#else +typedef BlurFilter = flash.filters.BlurFilter; +#end From add639d01ae10dcac24d7d9008ac0e58af1c7570 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Fri, 30 Jan 2026 17:01:14 +0800 Subject: [PATCH 02/13] Make the shader compatible with more devices. --- source/funkin/backend/shaders/BloomEffect.hx | 84 ++++++++++++-------- 1 file changed, 49 insertions(+), 35 deletions(-) diff --git a/source/funkin/backend/shaders/BloomEffect.hx b/source/funkin/backend/shaders/BloomEffect.hx index 8084e46bd..011f81aa3 100644 --- a/source/funkin/backend/shaders/BloomEffect.hx +++ b/source/funkin/backend/shaders/BloomEffect.hx @@ -160,28 +160,28 @@ import lime._internal.graphics.ImageDataUtil; // TODO create a soft, unfocused image. @param blurX The amount to blur horizontally. Valid values are from 0 to - 255.0(floating-point value). + 255.0(floating-point value). @param blurY The amount to blur vertically. Valid values are from 0 to - 255.0(floating-point value). + 255.0(floating-point value). @param quality The number of times to apply the filter. You can specify - the quality using the BitmapFilterQuality constants: + the quality using the BitmapFilterQuality constants: - * `openfl.filters.BitmapFilterQuality.LOW` + * `openfl.filters.BitmapFilterQuality.LOW` - * `openfl.filters.BitmapFilterQuality.MEDIUM` + * `openfl.filters.BitmapFilterQuality.MEDIUM` - * `openfl.filters.BitmapFilterQuality.HIGH` + * `openfl.filters.BitmapFilterQuality.HIGH` - High quality approximates a Gaussian blur. For most - applications, these three values are sufficient. Although - you can use additional numeric values up to 15 to achieve - different effects, be aware that higher values are rendered - more slowly. + High quality approximates a Gaussian blur. For most + applications, these three values are sufficient. Although + you can use additional numeric values up to 15 to achieve + different effects, be aware that higher values are rendered + more slowly. @param strength The intensity of the bloom effect. Default is 1.0. @param threshold The brightness threshold for bloom. Pixels brighter than - this value will bloom. Default is 0.5. + this value will bloom. Default is 0.5. **/ public function new(blurX:Float = 4, blurY:Float = 4, quality:Float = 1, strength:Float = 1.0, threshold:Float = 0.5) { @@ -343,45 +343,59 @@ import lime._internal.graphics.ImageDataUtil; // TODO #end private class BlurShader extends BitmapFilterShader { - @:glFragmentSource("uniform sampler2D openfl_Texture; + @:glFragmentSource(" + uniform sampler2D openfl_Texture; - varying vec2 vBlurCoords[7]; + varying vec2 vBlurCoord0; + varying vec2 vBlurCoord1; + varying vec2 vBlurCoord2; + varying vec2 vBlurCoord3; + varying vec2 vBlurCoord4; + varying vec2 vBlurCoord5; + varying vec2 vBlurCoord6; void main(void) { - vec4 sum = vec4(0.0); - sum += texture2D(openfl_Texture, vBlurCoords[0]) * 0.00443; - sum += texture2D(openfl_Texture, vBlurCoords[1]) * 0.05399; - sum += texture2D(openfl_Texture, vBlurCoords[2]) * 0.24197; - sum += texture2D(openfl_Texture, vBlurCoords[3]) * 0.39894; - sum += texture2D(openfl_Texture, vBlurCoords[4]) * 0.24197; - sum += texture2D(openfl_Texture, vBlurCoords[5]) * 0.05399; - sum += texture2D(openfl_Texture, vBlurCoords[6]) * 0.00443; + vec4 sum = texture2D(openfl_Texture, vBlurCoord0) * 0.00443; + sum += texture2D(openfl_Texture, vBlurCoord1) * 0.05399; + sum += texture2D(openfl_Texture, vBlurCoord2) * 0.24197; + sum += texture2D(openfl_Texture, vBlurCoord3) * 0.39894; + sum += texture2D(openfl_Texture, vBlurCoord4) * 0.24197; + sum += texture2D(openfl_Texture, vBlurCoord5) * 0.05399; + sum += texture2D(openfl_Texture, vBlurCoord6) * 0.00443; gl_FragColor = sum; - }") - @:glVertexSource("attribute vec4 openfl_Position; + } + ") + @:glVertexSource(" + attribute vec4 openfl_Position; attribute vec2 openfl_TextureCoord; uniform mat4 openfl_Matrix; uniform vec2 uRadius; - varying vec2 vBlurCoords[7]; uniform vec2 uTextureSize; - void main(void) { + varying vec2 vBlurCoord0; + varying vec2 vBlurCoord1; + varying vec2 vBlurCoord2; + varying vec2 vBlurCoord3; + varying vec2 vBlurCoord4; + varying vec2 vBlurCoord5; + varying vec2 vBlurCoord6; + void main(void) { gl_Position = openfl_Matrix * openfl_Position; vec2 r = uRadius / uTextureSize; - vBlurCoords[0] = openfl_TextureCoord - r; - vBlurCoords[1] = openfl_TextureCoord - r * 0.75; - vBlurCoords[2] = openfl_TextureCoord - r * 0.5; - vBlurCoords[3] = openfl_TextureCoord; - vBlurCoords[4] = openfl_TextureCoord + r * 0.5; - vBlurCoords[5] = openfl_TextureCoord + r * 0.75; - vBlurCoords[6] = openfl_TextureCoord + r; - - }") + vBlurCoord0 = openfl_TextureCoord - r; + vBlurCoord1 = openfl_TextureCoord - r * 0.75; + vBlurCoord2 = openfl_TextureCoord - r * 0.5; + vBlurCoord3 = openfl_TextureCoord; + vBlurCoord4 = openfl_TextureCoord + r * 0.5; + vBlurCoord5 = openfl_TextureCoord + r * 0.75; + vBlurCoord6 = openfl_TextureCoord + r; + } + ") public function new() { super(); From 2ac53e535173853bf282c8529a2610f130d73716 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Fri, 30 Jan 2026 17:03:18 +0800 Subject: [PATCH 03/13] Default --- source/funkin/backend/shaders/BloomEffect.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/funkin/backend/shaders/BloomEffect.hx b/source/funkin/backend/shaders/BloomEffect.hx index 011f81aa3..0200607da 100644 --- a/source/funkin/backend/shaders/BloomEffect.hx +++ b/source/funkin/backend/shaders/BloomEffect.hx @@ -181,9 +181,9 @@ import lime._internal.graphics.ImageDataUtil; // TODO more slowly. @param strength The intensity of the bloom effect. Default is 1.0. @param threshold The brightness threshold for bloom. Pixels brighter than - this value will bloom. Default is 0.5. + this value will bloom. Default is 0.6. **/ - public function new(blurX:Float = 4, blurY:Float = 4, quality:Float = 1, strength:Float = 1.0, threshold:Float = 0.5) + public function new(blurX:Float = 4, blurY:Float = 4, quality:Float = 0.5, strength:Float = 1.0, threshold:Float = 0.6) { super(); From 2b10e0eeea272bb9fa8c48f6bb2a3c87878bda12 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Fri, 30 Jan 2026 17:03:35 +0800 Subject: [PATCH 04/13] Update BloomEffect.hx --- source/funkin/backend/shaders/BloomEffect.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/backend/shaders/BloomEffect.hx b/source/funkin/backend/shaders/BloomEffect.hx index 0200607da..b391246ed 100644 --- a/source/funkin/backend/shaders/BloomEffect.hx +++ b/source/funkin/backend/shaders/BloomEffect.hx @@ -183,7 +183,7 @@ import lime._internal.graphics.ImageDataUtil; // TODO @param threshold The brightness threshold for bloom. Pixels brighter than this value will bloom. Default is 0.6. **/ - public function new(blurX:Float = 4, blurY:Float = 4, quality:Float = 0.5, strength:Float = 1.0, threshold:Float = 0.6) + public function new(blurX:Float = 10, blurY:Float = 10, quality:Float = 0.5, strength:Float = 1.0, threshold:Float = 0.6) { super(); From fe1f4a979574fbf6b1743b1264e8d862c34803af Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Fri, 30 Jan 2026 17:06:38 +0800 Subject: [PATCH 05/13] no __applyFilter --- source/funkin/backend/shaders/BloomEffect.hx | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/source/funkin/backend/shaders/BloomEffect.hx b/source/funkin/backend/shaders/BloomEffect.hx index b391246ed..bba6a043b 100644 --- a/source/funkin/backend/shaders/BloomEffect.hx +++ b/source/funkin/backend/shaders/BloomEffect.hx @@ -183,7 +183,7 @@ import lime._internal.graphics.ImageDataUtil; // TODO @param threshold The brightness threshold for bloom. Pixels brighter than this value will bloom. Default is 0.6. **/ - public function new(blurX:Float = 10, blurY:Float = 10, quality:Float = 0.5, strength:Float = 1.0, threshold:Float = 0.6) + public function new(blurX:Float = 10, blurY:Float = 10, quality:Float = 0.5, strength:Float = 1, threshold:Float = 0.6) { super(); @@ -203,18 +203,10 @@ import lime._internal.graphics.ImageDataUtil; // TODO return new BloomEffect(__blurX, __blurY, __quality, __strength, __threshold); } - @:noCompletion private override function __applyFilter(bitmapData:BitmapData, sourceBitmapData:BitmapData, sourceRect:Rectangle, - destPoint:Point):BitmapData + @:noCompletion private override function __applyFilter(bitmapData:BitmapData, sourceBitmapData:BitmapData, sourceRect:Rectangle, destPoint:Point):BitmapData { - #if lime - var time = Timer.stamp(); - var finalImage = ImageDataUtil.gaussianBlur(bitmapData.image, sourceBitmapData.image, sourceRect.__toLimeRectangle(), destPoint.__toLimeVector2(), - __blurX, __blurY, __quality); - var elapsed = Timer.stamp() - time; - // trace("blurX: " + __blurX + " blurY: " + __blurY + " quality: " + __quality + " elapsed: " + elapsed * 1000 + "ms"); - if (finalImage == bitmapData.image) return bitmapData; - #end - return sourceBitmapData; + trace('BloomEffect does not support bitmapData rendering functionality.') + return; } @:noCompletion private override function __initShader(renderer:DisplayObjectRenderer, pass:Int, sourceBitmapData:BitmapData):Shader From 5cdb93fe2b02a9b55a8331af182133ac7676de64 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Fri, 30 Jan 2026 17:09:46 +0800 Subject: [PATCH 06/13] Update BloomEffect.hx --- source/funkin/backend/shaders/BloomEffect.hx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/source/funkin/backend/shaders/BloomEffect.hx b/source/funkin/backend/shaders/BloomEffect.hx index bba6a043b..9bd4822a7 100644 --- a/source/funkin/backend/shaders/BloomEffect.hx +++ b/source/funkin/backend/shaders/BloomEffect.hx @@ -9,10 +9,6 @@ import openfl.display.DisplayObjectRenderer; import openfl.display.Shader; import openfl.geom.Point; import openfl.geom.Rectangle; -#if lime -import lime._internal.graphics.ImageDataUtil; // TODO - -#end /** The BlurFilter class lets you apply a blur visual effect to display @@ -206,7 +202,7 @@ import lime._internal.graphics.ImageDataUtil; // TODO @:noCompletion private override function __applyFilter(bitmapData:BitmapData, sourceBitmapData:BitmapData, sourceRect:Rectangle, destPoint:Point):BitmapData { trace('BloomEffect does not support bitmapData rendering functionality.') - return; + return sourceBitmapData; } @:noCompletion private override function __initShader(renderer:DisplayObjectRenderer, pass:Int, sourceBitmapData:BitmapData):Shader From 7014a1587fb32ec5eb504af0e7af956d4866d2f9 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Fri, 30 Jan 2026 17:21:12 +0800 Subject: [PATCH 07/13] Update BloomEffect.hx --- source/funkin/backend/shaders/BloomEffect.hx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/funkin/backend/shaders/BloomEffect.hx b/source/funkin/backend/shaders/BloomEffect.hx index 9bd4822a7..abdeceff5 100644 --- a/source/funkin/backend/shaders/BloomEffect.hx +++ b/source/funkin/backend/shaders/BloomEffect.hx @@ -435,8 +435,9 @@ private class ExtractShader extends BitmapFilterShader public function new() { super(); + #if !macro - uThreshold.value = [0.5]; + uThreshold.value = [0.6]; #end } } @@ -476,10 +477,11 @@ private class CombineShader extends BitmapFilterShader public function new() { super(); + #if !macro offset.value = [0, 0]; uStrength.value = [1.0]; - uThreshold.value = [0.5]; + uThreshold.value = [0.6]; #end } } From 3ab0386d4e15265fda1c60b293aa15c256869d55 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Sat, 31 Jan 2026 02:45:10 +0800 Subject: [PATCH 08/13] Performance improvement: 1.35x. --- source/funkin/backend/shaders/BloomEffect.hx | 28 +++++++------------- 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/source/funkin/backend/shaders/BloomEffect.hx b/source/funkin/backend/shaders/BloomEffect.hx index abdeceff5..c09fbd88f 100644 --- a/source/funkin/backend/shaders/BloomEffect.hx +++ b/source/funkin/backend/shaders/BloomEffect.hx @@ -201,7 +201,7 @@ import openfl.geom.Rectangle; @:noCompletion private override function __applyFilter(bitmapData:BitmapData, sourceBitmapData:BitmapData, sourceRect:Rectangle, destPoint:Point):BitmapData { - trace('BloomEffect does not support bitmapData rendering functionality.') + trace('BloomEffect does not support bitmapData rendering functionality.'); return sourceBitmapData; } @@ -339,17 +339,13 @@ private class BlurShader extends BitmapFilterShader varying vec2 vBlurCoord2; varying vec2 vBlurCoord3; varying vec2 vBlurCoord4; - varying vec2 vBlurCoord5; - varying vec2 vBlurCoord6; void main(void) { - vec4 sum = texture2D(openfl_Texture, vBlurCoord0) * 0.00443; - sum += texture2D(openfl_Texture, vBlurCoord1) * 0.05399; - sum += texture2D(openfl_Texture, vBlurCoord2) * 0.24197; - sum += texture2D(openfl_Texture, vBlurCoord3) * 0.39894; - sum += texture2D(openfl_Texture, vBlurCoord4) * 0.24197; - sum += texture2D(openfl_Texture, vBlurCoord5) * 0.05399; - sum += texture2D(openfl_Texture, vBlurCoord6) * 0.00443; + vec4 sum = texture2D(openfl_Texture, vBlurCoord0) * 0.05449; + sum += texture2D(openfl_Texture, vBlurCoord1) * 0.24420; + sum += texture2D(openfl_Texture, vBlurCoord2) * 0.40262; + sum += texture2D(openfl_Texture, vBlurCoord3) * 0.24420; + sum += texture2D(openfl_Texture, vBlurCoord4) * 0.05449; gl_FragColor = sum; } @@ -368,20 +364,16 @@ private class BlurShader extends BitmapFilterShader varying vec2 vBlurCoord2; varying vec2 vBlurCoord3; varying vec2 vBlurCoord4; - varying vec2 vBlurCoord5; - varying vec2 vBlurCoord6; void main(void) { gl_Position = openfl_Matrix * openfl_Position; vec2 r = uRadius / uTextureSize; vBlurCoord0 = openfl_TextureCoord - r; - vBlurCoord1 = openfl_TextureCoord - r * 0.75; - vBlurCoord2 = openfl_TextureCoord - r * 0.5; - vBlurCoord3 = openfl_TextureCoord; - vBlurCoord4 = openfl_TextureCoord + r * 0.5; - vBlurCoord5 = openfl_TextureCoord + r * 0.75; - vBlurCoord6 = openfl_TextureCoord + r; + vBlurCoord1 = openfl_TextureCoord - r * 0.5; + vBlurCoord2 = openfl_TextureCoord; + vBlurCoord3 = openfl_TextureCoord + r * 0.5; + vBlurCoord4 = openfl_TextureCoord + r; } ") public function new() From b2118f0584bbcddb00a837ad36ef52bdc6e3e309 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Sat, 31 Jan 2026 17:48:13 +0800 Subject: [PATCH 09/13] quality --- source/funkin/backend/shaders/BloomEffect.hx | 26 +++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/source/funkin/backend/shaders/BloomEffect.hx b/source/funkin/backend/shaders/BloomEffect.hx index c09fbd88f..4b49c59b1 100644 --- a/source/funkin/backend/shaders/BloomEffect.hx +++ b/source/funkin/backend/shaders/BloomEffect.hx @@ -109,7 +109,7 @@ import openfl.geom.Rectangle; * `BitmapFilterQuality.MEDIUM` * `BitmapFilterQuality.HIGH` **/ - public var quality(get, set):Float; + public var quality(get, set):Int; public var strength(get, set):Float; @@ -118,7 +118,7 @@ import openfl.geom.Rectangle; @:noCompletion private var __blurX:Float; @:noCompletion private var __blurY:Float; @:noCompletion private var __horizontalPasses:Int; - @:noCompletion private var __quality:Float; + @:noCompletion private var __quality:Int; @:noCompletion private var __verticalPasses:Int; @:noCompletion private var __strength:Float; @:noCompletion private var __threshold:Float; @@ -179,7 +179,7 @@ import openfl.geom.Rectangle; @param threshold The brightness threshold for bloom. Pixels brighter than this value will bloom. Default is 0.6. **/ - public function new(blurX:Float = 10, blurY:Float = 10, quality:Float = 0.5, strength:Float = 1, threshold:Float = 0.6) + public function new(blurX:Float = 10, blurY:Float = 10, quality:Int = 4, strength:Float = 1, threshold:Float = 0.6) { super(); @@ -253,8 +253,12 @@ import openfl.geom.Rectangle; { __blurX = value; __renderDirty = true; - __leftExtension = (value > 0 ? Math.ceil(value) : 0); - __rightExtension = __leftExtension; + // __leftExtension = (value > 0 ? Math.ceil(value) : 0); + // __rightExtension = __leftExtension; + + __horizontalPasses = (__blurX <= 0) ? 0 : Math.round(__blurX * (0.5 / 4)) + 1; + + __numShaderPasses = __horizontalPasses + __verticalPasses + 2; } return value; } @@ -270,18 +274,22 @@ import openfl.geom.Rectangle; { __blurY = value; __renderDirty = true; - __topExtension = (value > 0 ? Math.ceil(value) : 0); - __bottomExtension = __topExtension; + // __topExtension = (value > 0 ? Math.ceil(value) : 0); + // __bottomExtension = __topExtension; + + __verticalPasses = (__blurY <= 0) ? 0 : Math.round(__blurY * (0.5 / 4)) + 1; + + __numShaderPasses = __horizontalPasses + __verticalPasses + 2; } return value; } - @:noCompletion private function get_quality():Float + @:noCompletion private function get_quality():Int { return __quality; } - @:noCompletion private function set_quality(value:Float):Float + @:noCompletion private function set_quality(value:Int):Int { // TODO: Quality effect with fewer passes? From d29372a9f70c613d8fcdbd82627dfb76e21ee71b Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Sun, 1 Feb 2026 02:00:31 +0800 Subject: [PATCH 10/13] Implemented low-resolution bloom, achieving a 10x performance boost!!! --- source/funkin/backend/shaders/BloomEffect.hx | 159 ++++++++++++++----- 1 file changed, 116 insertions(+), 43 deletions(-) diff --git a/source/funkin/backend/shaders/BloomEffect.hx b/source/funkin/backend/shaders/BloomEffect.hx index 4b49c59b1..5877992c2 100644 --- a/source/funkin/backend/shaders/BloomEffect.hx +++ b/source/funkin/backend/shaders/BloomEffect.hx @@ -109,19 +109,22 @@ import openfl.geom.Rectangle; * `BitmapFilterQuality.MEDIUM` * `BitmapFilterQuality.HIGH` **/ - public var quality(get, set):Int; + public var quality(get, set):Float; public var strength(get, set):Float; public var threshold(get, set):Float; + public var extension(get, set):Bool; + @:noCompletion private var __blurX:Float; @:noCompletion private var __blurY:Float; @:noCompletion private var __horizontalPasses:Int; - @:noCompletion private var __quality:Int; + @:noCompletion private var __quality:Float; @:noCompletion private var __verticalPasses:Int; @:noCompletion private var __strength:Float; @:noCompletion private var __threshold:Float; + @:noCompletion private var __extension:Bool; #if openfljs @:noCompletion private static function __init__() @@ -179,7 +182,7 @@ import openfl.geom.Rectangle; @param threshold The brightness threshold for bloom. Pixels brighter than this value will bloom. Default is 0.6. **/ - public function new(blurX:Float = 10, blurY:Float = 10, quality:Int = 4, strength:Float = 1, threshold:Float = 0.6) + public function new(blurX:Float = 10, blurY:Float = 10, quality:Float = 8, strength:Float = 1, threshold:Float = 0.6) { super(); @@ -188,6 +191,7 @@ import openfl.geom.Rectangle; this.quality = quality; this.strength = strength; this.threshold = threshold; + this.extension = false; __needSecondBitmapData = true; __preserveObject = true; @@ -214,6 +218,7 @@ import openfl.geom.Rectangle; { case 0: __extractShader.uThreshold.value = [__threshold]; + __extractShader.uQuality.value = [__quality]; return __extractShader; case _ if (pass <= numBlurPasses): @@ -225,7 +230,8 @@ import openfl.geom.Rectangle; final scale = Math.pow(0.5, scalePass >> 1); final blurRadius = isHorizontal ? blurX * scale : blurY * scale; - __blurShader.uRadius.value = isHorizontal ? [blurRadius, 0.0] : [0.0, blurRadius]; + __blurShader.uRadius.value = isHorizontal ? [blurRadius / __quality, 0.0] : [0.0, blurRadius / __quality]; + __blurShader.uQuality.value = [__quality]; return __blurShader; @@ -234,6 +240,7 @@ import openfl.geom.Rectangle; __combineShader.offset.value = [0.0, 0.0]; __combineShader.uStrength.value = [__strength]; __combineShader.uThreshold.value = [__threshold]; + __combineShader.uQuality.value = [__quality]; return __combineShader; } #else @@ -253,11 +260,20 @@ import openfl.geom.Rectangle; { __blurX = value; __renderDirty = true; - // __leftExtension = (value > 0 ? Math.ceil(value) : 0); - // __rightExtension = __leftExtension; - - __horizontalPasses = (__blurX <= 0) ? 0 : Math.round(__blurX * (0.5 / 4)) + 1; + if (!__extension) + { + // Setting it to 1 prevents bloom flickering at the screen edges + __leftExtension = 1; + __rightExtension = 1; + } + else + { + __leftExtension = (value > 0 ? Math.ceil(value) : 0); + __rightExtension = __leftExtension; + } + + __horizontalPasses = (value <= 0) ? 0 : Math.ceil(value * 0.0625 / quality) + 1; __numShaderPasses = __horizontalPasses + __verticalPasses + 2; } return value; @@ -274,31 +290,38 @@ import openfl.geom.Rectangle; { __blurY = value; __renderDirty = true; - // __topExtension = (value > 0 ? Math.ceil(value) : 0); - // __bottomExtension = __topExtension; - - __verticalPasses = (__blurY <= 0) ? 0 : Math.round(__blurY * (0.5 / 4)) + 1; + if (!__extension) + { + // Setting it to 1 prevents bloom flickering at the screen edges + __topExtension = 1; + __bottomExtension = 1; + } + else + { + __topExtension = (value > 0 ? Math.ceil(value) : 0); + __bottomExtension = __topExtension; + } + + __verticalPasses = (value <= 0) ? 0 : Math.ceil(value * 0.0625 / quality) + 1; __numShaderPasses = __horizontalPasses + __verticalPasses + 2; } return value; } - @:noCompletion private function get_quality():Int + @:noCompletion private function get_quality():Float { return __quality; } - @:noCompletion private function set_quality(value:Int):Int + @:noCompletion private function set_quality(value:Float):Float { - // TODO: Quality effect with fewer passes? - - __horizontalPasses = (__blurX <= 0) ? 0 : Math.round(__blurX * (value / 4)) + 1; - __verticalPasses = (__blurY <= 0) ? 0 : Math.round(__blurY * (value / 4)) + 1; - + __horizontalPasses = (__blurX <= 0) ? 0 : Math.round(__blurX * 0.125 / value) + 1; + __verticalPasses = (__blurY <= 0) ? 0 : Math.round(__blurY * 0.125 / value) + 1; __numShaderPasses = __horizontalPasses + __verticalPasses + 2; - if (value != __quality) __renderDirty = true; + if (value != __quality) + __renderDirty = true; return __quality = value; } @@ -331,6 +354,38 @@ import openfl.geom.Rectangle; } return value; } + + @:noCompletion private function get_extension():Bool + { + return __extension; + } + + @:noCompletion private function set_extension(value:Bool):Bool + { + if (value != __extension) + { + __extension = value; + + if (!value) + { + // Setting it to 1 prevents bloom flickering at the screen edges + __leftExtension = 1; + __rightExtension = 1; + __topExtension = 1; + __bottomExtension = 1; + } + else + { + __leftExtension = (__blurX > 0 ? Math.ceil(__blurX) : 0); + __rightExtension = __leftExtension; + __topExtension = (__blurY > 0 ? Math.ceil(__blurY) : 0); + __bottomExtension = __topExtension; + } + + __renderDirty = true; + } + return value; + } } #if !openfl_debug @@ -342,19 +397,23 @@ private class BlurShader extends BitmapFilterShader @:glFragmentSource(" uniform sampler2D openfl_Texture; - varying vec2 vBlurCoord0; - varying vec2 vBlurCoord1; + varying mat2 vBlurCoord0; + varying mat2 vBlurCoord1; varying vec2 vBlurCoord2; - varying vec2 vBlurCoord3; - varying vec2 vBlurCoord4; + varying mat2 vBlurCoord3; + varying mat2 vBlurCoord4; void main(void) { - vec4 sum = texture2D(openfl_Texture, vBlurCoord0) * 0.05449; - sum += texture2D(openfl_Texture, vBlurCoord1) * 0.24420; - sum += texture2D(openfl_Texture, vBlurCoord2) * 0.40262; - sum += texture2D(openfl_Texture, vBlurCoord3) * 0.24420; - sum += texture2D(openfl_Texture, vBlurCoord4) * 0.05449; - + if ((all(greaterThanEqual(vBlurCoord2, vec2(0.0))) && all(lessThanEqual(vBlurCoord2, vec2(1.0)))) == false) return; + vec4 sum = texture2D(openfl_Texture, vBlurCoord0[0]) * 0.028532; + sum += texture2D(openfl_Texture, vBlurCoord0[1]) * 0.067234; + sum += texture2D(openfl_Texture, vBlurCoord1[0]) * 0.124009; + sum += texture2D(openfl_Texture, vBlurCoord1[1]) * 0.179044; + sum += texture2D(openfl_Texture, vBlurCoord2) * 0.202360; + sum += texture2D(openfl_Texture, vBlurCoord3[0]) * 0.179044; + sum += texture2D(openfl_Texture, vBlurCoord3[1]) * 0.124009; + sum += texture2D(openfl_Texture, vBlurCoord4[0]) * 0.067234; + sum += texture2D(openfl_Texture, vBlurCoord4[1]) * 0.028532; gl_FragColor = sum; } ") @@ -366,22 +425,30 @@ private class BlurShader extends BitmapFilterShader uniform vec2 uRadius; uniform vec2 uTextureSize; + uniform float uQuality; - varying vec2 vBlurCoord0; - varying vec2 vBlurCoord1; + varying mat2 vBlurCoord0; + varying mat2 vBlurCoord1; varying vec2 vBlurCoord2; - varying vec2 vBlurCoord3; - varying vec2 vBlurCoord4; + varying mat2 vBlurCoord3; + varying mat2 vBlurCoord4; void main(void) { - gl_Position = openfl_Matrix * openfl_Position; + vec4 pos = openfl_Position; + pos.xy /= uQuality; + gl_Position = openfl_Matrix * pos; vec2 r = uRadius / uTextureSize; - vBlurCoord0 = openfl_TextureCoord - r; - vBlurCoord1 = openfl_TextureCoord - r * 0.5; - vBlurCoord2 = openfl_TextureCoord; - vBlurCoord3 = openfl_TextureCoord + r * 0.5; - vBlurCoord4 = openfl_TextureCoord + r; + vec2 coord = openfl_TextureCoord / uQuality; + vBlurCoord0[0] = coord - r; + vBlurCoord0[1] = coord - r * 0.25; + vBlurCoord1[0] = coord - r * 0.5; + vBlurCoord1[1] = coord - r * 0.75; + vBlurCoord2 = coord; + vBlurCoord3[0] = coord + r * 0.25; + vBlurCoord3[1] = coord + r * 0.5; + vBlurCoord4[0] = coord + r * 0.75; + vBlurCoord4[1] = coord + r; } ") public function new() @@ -415,6 +482,7 @@ private class ExtractShader extends BitmapFilterShader varying vec2 vTexCoord; void main(void) { + if ((all(greaterThanEqual(vTexCoord, vec2(0.0))) && all(lessThanEqual(vTexCoord, vec2(1.0)))) == false) return; vec4 texel = texture2D(openfl_Texture, vTexCoord); float brightness = max(max(texel.r, texel.g), texel.b); float mask = smoothstep(uThreshold, uThreshold + 0.1, brightness); @@ -425,10 +493,14 @@ private class ExtractShader extends BitmapFilterShader attribute vec4 openfl_Position; attribute vec2 openfl_TextureCoord; uniform mat4 openfl_Matrix; + uniform vec2 openfl_TextureSize; + uniform float uQuality; varying vec2 vTexCoord; void main(void) { - gl_Position = openfl_Matrix * openfl_Position; + vec4 pos = openfl_Position; + pos.xy /= uQuality; + gl_Position = openfl_Matrix * pos; vTexCoord = openfl_TextureCoord; } ") @@ -467,11 +539,12 @@ private class CombineShader extends BitmapFilterShader uniform mat4 openfl_Matrix; uniform vec2 openfl_TextureSize; uniform vec2 offset; + uniform float uQuality; varying vec4 textureCoords; void main(void) { gl_Position = openfl_Matrix * openfl_Position; - textureCoords = vec4(openfl_TextureCoord, openfl_TextureCoord - offset / openfl_TextureSize); + textureCoords = vec4(openfl_TextureCoord, (openfl_TextureCoord - offset / openfl_TextureSize) / uQuality); } ") public function new() @@ -487,4 +560,4 @@ private class CombineShader extends BitmapFilterShader } #else typedef BlurFilter = flash.filters.BlurFilter; -#end +#end \ No newline at end of file From caaf2df546efcc7a885f7540cba1379bd79860d5 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Sun, 1 Feb 2026 12:41:35 +0800 Subject: [PATCH 11/13] 1 --- source/funkin/backend/shaders/BloomEffect.hx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/funkin/backend/shaders/BloomEffect.hx b/source/funkin/backend/shaders/BloomEffect.hx index 5877992c2..42729ce55 100644 --- a/source/funkin/backend/shaders/BloomEffect.hx +++ b/source/funkin/backend/shaders/BloomEffect.hx @@ -405,6 +405,7 @@ private class BlurShader extends BitmapFilterShader void main(void) { if ((all(greaterThanEqual(vBlurCoord2, vec2(0.0))) && all(lessThanEqual(vBlurCoord2, vec2(1.0)))) == false) return; + vec4 sum = texture2D(openfl_Texture, vBlurCoord0[0]) * 0.028532; sum += texture2D(openfl_Texture, vBlurCoord0[1]) * 0.067234; sum += texture2D(openfl_Texture, vBlurCoord1[0]) * 0.124009; @@ -483,6 +484,7 @@ private class ExtractShader extends BitmapFilterShader void main(void) { if ((all(greaterThanEqual(vTexCoord, vec2(0.0))) && all(lessThanEqual(vTexCoord, vec2(1.0)))) == false) return; + vec4 texel = texture2D(openfl_Texture, vTexCoord); float brightness = max(max(texel.r, texel.g), texel.b); float mask = smoothstep(uThreshold, uThreshold + 0.1, brightness); From e19a8edb3de3a61fa1db838678e57b5a5b9544c7 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Sun, 8 Feb 2026 23:15:04 +0800 Subject: [PATCH 12/13] useLowQualityExtract --- source/funkin/backend/shaders/BloomEffect.hx | 298 +++++++++++-------- 1 file changed, 170 insertions(+), 128 deletions(-) diff --git a/source/funkin/backend/shaders/BloomEffect.hx b/source/funkin/backend/shaders/BloomEffect.hx index 42729ce55..78840a626 100644 --- a/source/funkin/backend/shaders/BloomEffect.hx +++ b/source/funkin/backend/shaders/BloomEffect.hx @@ -1,7 +1,6 @@ package funkin.backend.shaders; import haxe.Timer; -#if !flash import openfl.filters.BitmapFilter; import openfl.filters.BitmapFilterShader; import openfl.display.BitmapData; @@ -11,59 +10,44 @@ import openfl.geom.Point; import openfl.geom.Rectangle; /** - The BlurFilter class lets you apply a blur visual effect to display - objects. A blur effect softens the details of an image. You can produce - blurs that range from a softly unfocused look to a Gaussian blur, a hazy - appearance like viewing an image through semi-opaque glass. When the - `quality` property of this filter is set to low, the result is a - softly unfocused look. When the `quality` property is set to - high, it approximates a Gaussian blur filter. You can apply the filter to - any display object(that is, objects that inherit from the DisplayObject - class), such as MovieClip, SimpleButton, TextField, and Video objects, as - well as to BitmapData objects. - - To create a new filter, use the constructor `new - BlurFilter()`. The use of filters depends on the object to which you - apply the filter: - - - * To apply filters to movie clips, text fields, buttons, and video, use - the `filters` property(inherited from DisplayObject). Setting - the `filters` property of an object does not modify the object, - and you can remove the filter by clearing the `filters` - property. - * To apply filters to BitmapData objects, use the - `BitmapData.applyFilter()` method. Calling - `applyFilter()` on a BitmapData object takes the source - BitmapData object and the filter object and generates a filtered image as a - result. - - - If you apply a filter to a display object, the - `cacheAsBitmap` property of the display object is set to - `true`. If you remove all filters, the original value of - `cacheAsBitmap` is restored. - - This filter supports Stage scaling. However, it does not support general - scaling, rotation, and skewing. If the object itself is scaled - (`scaleX` and `scaleY` are not set to 100%), the - filter effect is not scaled. It is scaled only when the user zooms in on - the Stage. - - A filter is not applied if the resulting image exceeds the maximum - dimensions. In AIR 1.5 and Flash Player 10, the maximum is 8,191 pixels in - width or height, and the total number of pixels cannot exceed 16,777,215 - pixels.(So, if an image is 8,191 pixels wide, it can only be 2,048 pixels - high.) In Flash Player 9 and earlier and AIR 1.1 and earlier, the - limitation is 2,880 pixels in height and 2,880 pixels in width. If, for - example, you zoom in on a large movie clip with a filter applied, the - filter is turned off if the resulting image exceeds the maximum - dimensions. + The BloomEffect class applies a bloom/glow visual effect to display objects. + A bloom effect extracts bright areas from an image, blurs them, and combines + them back to create a glowing halo around bright objects. This effect is + commonly used to simulate intense light, emissive materials, or to add a + dreamy, atmospheric quality to scenes. + + The effect consists of three stages: + 1. Extraction - Bright pixels above a threshold are extracted + 2. Blurring - The extracted bright areas are blurred horizontally and vertically + 3. Combination - The blurred result is blended back with the original image + + You can apply the filter to any display object (objects that inherit from + DisplayObject), such as MovieClip, SimpleButton, TextField, and Video objects, + as well as to BitmapData objects. + + To create a new filter, use the constructor `new BloomEffect()`. The usage + depends on the target object: + + * For display objects: Use the `filters` property (inherited from DisplayObject). + Setting `filters` doesn't modify the object, and filters can be removed by + clearing the `filters` property. + * For BitmapData objects: Use the `BitmapData.applyFilter()` method, which + takes the source BitmapData and filter object, generating a filtered result. + + Applying a filter to a display object sets its `cacheAsBitmap` property to + `true`. Removing all filters restores the original `cacheAsBitmap` value. + + This filter supports Stage scaling but not general scaling, rotation, or skewing. + If the object itself is scaled (`scaleX` and `scaleY` ≠ 100%), the filter + effect doesn't scale—it only scales when the Stage is zoomed. + + A filter won't be applied if the resulting image exceeds maximum dimensions: + * AIR 1.5/Flash Player 10+: 8,191px width/height, 16,777,215 total pixels + * Flash Player 9/AIR 1.1-: 2,880px width/height + + For example, zooming into a large filtered movie clip may disable the filter + if the resulting image exceeds these limits. **/ -#if !openfl_debug -@:fileXml('tags="haxe,release"') -@:noDebug -#end @:access(openfl.geom.Point) @:access(openfl.geom.Rectangle) @:final class BloomEffect extends BitmapFilter @@ -71,52 +55,50 @@ import openfl.geom.Rectangle; @:noCompletion private static var __blurShader:BlurShader = new BlurShader(); @:noCompletion private static var __combineShader:CombineShader = new CombineShader(); @:noCompletion private static var __extractShader:ExtractShader = new ExtractShader(); + @:noCompletion private static var __extractLowShader:ExtractLowShader = new ExtractLowShader(); /** - The amount of horizontal blur. Valid values are from 0 to 255(floating - point). The default value is 4. Values that are a power of 2(such as 2, - 4, 8, 16 and 32) are optimized to render more quickly than other values. + Values that are a power of 2 (such as 2, 4, 8, 16 and 32) are optimized to render + more quickly than other values. **/ public var blurX(get, set):Float; /** - The amount of vertical blur. Valid values are from 0 to 255(floating - point). The default value is 4. Values that are a power of 2(such as 2, - 4, 8, 16 and 32) are optimized to render more quickly than other values. + Values that are a power of 2 (such as 2, 4, 8, 16 and 32) are optimized to render + more quickly than other values. **/ public var blurY(get, set):Float; /** - The number of times to perform the blur. The default value is - `BitmapFilterQuality.LOW`, which is equivalent to applying the - filter once. The value `BitmapFilterQuality.MEDIUM` applies the - filter twice; the value `BitmapFilterQuality.HIGH` applies it - three times and approximates a Gaussian blur. Filters with lower values - are rendered more quickly. - - For most applications, a `quality` value of low, medium, or - high is sufficient. Although you can use additional numeric values up to - 15 to increase the number of times the blur is applied, higher values are - rendered more slowly. Instead of increasing the value of - `quality`, you can often get a similar effect, and with faster - rendering, by simply increasing the values of the `blurX` and - `blurY` properties. - - You can use the following BitmapFilterQuality constants to specify - values of the `quality` property: - - * `BitmapFilterQuality.LOW` - * `BitmapFilterQuality.MEDIUM` - * `BitmapFilterQuality.HIGH` + The downscaling factor for bloom rendering. Higher values significantly reduce + GPU performance cost, but setting values too high may cause noticeable flickering. + Recommended range is 8-24. **/ public var quality(get, set):Float; + /** + The intensity of the bloom effect. Higher values produce more pronounced bloom. + **/ public var strength(get, set):Float; + /** + The brightness threshold for bloom extraction. Pixels brighter than this value + will contribute to the bloom effect. Value range is 0.0 to 1.0. + **/ public var threshold(get, set):Float; + /** + Enables extended rendering area to avoid edge artifacts. Enabling this option + will increase performance cost. Generally not required when rendering to camera. + **/ public var extension(get, set):Bool; + /** + Enables low-quality extraction to reduce performance cost. When disabled, + reduces flickering but has higher performance impact on non-desktop platforms. + **/ + public var useLowQualityExtract(get, set):Bool; + @:noCompletion private var __blurX:Float; @:noCompletion private var __blurY:Float; @:noCompletion private var __horizontalPasses:Int; @@ -125,6 +107,7 @@ import openfl.geom.Rectangle; @:noCompletion private var __strength:Float; @:noCompletion private var __threshold:Float; @:noCompletion private var __extension:Bool; + @:noCompletion private var __useLowQualityExtract:Bool; #if openfljs @:noCompletion private static function __init__() @@ -150,39 +133,27 @@ import openfl.geom.Rectangle; get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_threshold (); }"), set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_threshold (v); }") }, + "useLowQualityExtract": { + get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_useLowQualityExtract (); }"), + set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_useLowQualityExtract (v); }") + }, }); } #end /** - Initializes the filter with the specified parameters. The default values - create a soft, unfocused image. - - @param blurX The amount to blur horizontally. Valid values are from 0 to - 255.0(floating-point value). - @param blurY The amount to blur vertically. Valid values are from 0 to - 255.0(floating-point value). - @param quality The number of times to apply the filter. You can specify - the quality using the BitmapFilterQuality constants: - - - * `openfl.filters.BitmapFilterQuality.LOW` - - * `openfl.filters.BitmapFilterQuality.MEDIUM` - - * `openfl.filters.BitmapFilterQuality.HIGH` - - - High quality approximates a Gaussian blur. For most - applications, these three values are sufficient. Although - you can use additional numeric values up to 15 to achieve - different effects, be aware that higher values are rendered - more slowly. - @param strength The intensity of the bloom effect. Default is 1.0. - @param threshold The brightness threshold for bloom. Pixels brighter than - this value will bloom. Default is 0.6. + Initializes the bloom filter with the specified parameters. + + @param blurX The amount to blur horizontally. + @param blurY The amount to blur vertically. + @param quality The downscaling factor for bloom rendering (higher values reduce + GPU cost but may cause flickering if too high). + @param strength The intensity of the bloom effect. + @param threshold The brightness threshold for bloom extraction (0.0 to 1.0). + @param useLowQualityExtract Enables performance-optimized extraction with + potentially more flickering. **/ - public function new(blurX:Float = 10, blurY:Float = 10, quality:Float = 8, strength:Float = 1, threshold:Float = 0.6) + public function new(blurX:Float = 10, blurY:Float = 10, quality:Float = #if desktop 24 #else 8 #end, strength:Float = 1, threshold:Float = 0.6, useLowQualityExtract:Bool = #if desktop false #else true #end) { super(); @@ -192,6 +163,7 @@ import openfl.geom.Rectangle; this.strength = strength; this.threshold = threshold; this.extension = false; + this.useLowQualityExtract = useLowQualityExtract; __needSecondBitmapData = true; __preserveObject = true; @@ -200,7 +172,7 @@ import openfl.geom.Rectangle; public override function clone():BitmapFilter { - return new BloomEffect(__blurX, __blurY, __quality, __strength, __threshold); + return new BloomEffect(__blurX, __blurY, __quality, __strength, __threshold, __useLowQualityExtract); } @:noCompletion private override function __applyFilter(bitmapData:BitmapData, sourceBitmapData:BitmapData, sourceRect:Rectangle, destPoint:Point):BitmapData @@ -217,9 +189,18 @@ import openfl.geom.Rectangle; switch pass { case 0: - __extractShader.uThreshold.value = [__threshold]; - __extractShader.uQuality.value = [__quality]; - return __extractShader; + if (__useLowQualityExtract) + { + __extractLowShader.uThreshold.value = [__threshold]; + __extractLowShader.uQuality.value = [__quality]; + return __extractLowShader; + } + else + { + __extractShader.uThreshold.value = [__threshold]; + __extractShader.uQuality.value = [__quality]; + return __extractShader; + } case _ if (pass <= numBlurPasses): final blurPass = pass - 1; @@ -386,12 +367,23 @@ import openfl.geom.Rectangle; } return value; } + + @:noCompletion private function get_useLowQualityExtract():Bool + { + return __useLowQualityExtract; + } + + @:noCompletion private function set_useLowQualityExtract(value:Bool):Bool + { + if (value != __useLowQualityExtract) + { + __useLowQualityExtract = value; + __renderDirty = true; + } + return value; + } } -#if !openfl_debug -@:fileXml('tags="haxe,release"') -@:noDebug -#end private class BlurShader extends BitmapFilterShader { @:glFragmentSource(" @@ -471,11 +463,7 @@ private class BlurShader extends BitmapFilterShader } } -#if !openfl_debug -@:fileXml('tags="haxe,release"') -@:noDebug -#end -private class ExtractShader extends BitmapFilterShader +private class ExtractLowShader extends BitmapFilterShader { @:glFragmentSource(" uniform sampler2D openfl_Texture; @@ -516,10 +504,67 @@ private class ExtractShader extends BitmapFilterShader } } -#if !openfl_debug -@:fileXml('tags="haxe,release"') -@:noDebug -#end +private class ExtractShader extends BitmapFilterShader +{ + @:glFragmentSource(" + uniform sampler2D openfl_Texture; + uniform vec2 openfl_TextureSize; + uniform float uThreshold; + uniform float uQuality; + varying vec2 vTexCoord; + + void main(void) { + if ((all(greaterThanEqual(vTexCoord, vec2(0.0))) && all(lessThanEqual(vTexCoord, vec2(1.0)))) == false) return; + + float quality = floor(uQuality) / 2.0; + vec2 texelSize = 1.0 / openfl_TextureSize; + + vec4 accumulated = vec4(0.0); + int sampleCount = 0; + + + for (float dx = -quality; dx <= quality; dx += 2.0) { + for (float dy = -quality; dy <= quality; dy += 2.0) { + vec2 sampleCoord = vTexCoord + vec2(dx, dy) * texelSize; + + if ((all(greaterThanEqual(sampleCoord, vec2(0.0))) && all(lessThanEqual(sampleCoord, vec2(1.0)))) == false) continue; + + vec4 texel = texture2D(openfl_Texture, sampleCoord); + float brightness = max(max(texel.r, texel.g), texel.b); + float mask = smoothstep(uThreshold, uThreshold + 0.1, brightness); + accumulated += texel * mask; + sampleCount++; + } + } + + gl_FragColor = accumulated / float(sampleCount); + } + ") + @:glVertexSource(" + attribute vec4 openfl_Position; + attribute vec2 openfl_TextureCoord; + uniform mat4 openfl_Matrix; + uniform vec2 openfl_TextureSize; + uniform float uQuality; + varying vec2 vTexCoord; + + void main(void) { + vec4 pos = openfl_Position; + pos.xy /= uQuality; + gl_Position = openfl_Matrix * pos; + vTexCoord = openfl_TextureCoord; + } + ") + public function new() + { + super(); + + #if !macro + uThreshold.value = [0.6]; + #end + } +} + private class CombineShader extends BitmapFilterShader { @:glFragmentSource(" @@ -559,7 +604,4 @@ private class CombineShader extends BitmapFilterShader uThreshold.value = [0.6]; #end } -} -#else -typedef BlurFilter = flash.filters.BlurFilter; -#end \ No newline at end of file +} \ No newline at end of file From 01c0aeacf87cc9aeefe4f52231fdb7ef1e785c91 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Mon, 9 Feb 2026 00:43:12 +0800 Subject: [PATCH 13/13] brightness --- source/funkin/backend/shaders/BloomEffect.hx | 33 +++++++++++--------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/source/funkin/backend/shaders/BloomEffect.hx b/source/funkin/backend/shaders/BloomEffect.hx index 78840a626..713a80839 100644 --- a/source/funkin/backend/shaders/BloomEffect.hx +++ b/source/funkin/backend/shaders/BloomEffect.hx @@ -15,42 +15,42 @@ import openfl.geom.Rectangle; them back to create a glowing halo around bright objects. This effect is commonly used to simulate intense light, emissive materials, or to add a dreamy, atmospheric quality to scenes. - + The effect consists of three stages: 1. Extraction - Bright pixels above a threshold are extracted 2. Blurring - The extracted bright areas are blurred horizontally and vertically 3. Combination - The blurred result is blended back with the original image - + You can apply the filter to any display object (objects that inherit from DisplayObject), such as MovieClip, SimpleButton, TextField, and Video objects, as well as to BitmapData objects. To create a new filter, use the constructor `new BloomEffect()`. The usage depends on the target object: - + * For display objects: Use the `filters` property (inherited from DisplayObject). Setting `filters` doesn't modify the object, and filters can be removed by clearing the `filters` property. * For BitmapData objects: Use the `BitmapData.applyFilter()` method, which takes the source BitmapData and filter object, generating a filtered result. - + Applying a filter to a display object sets its `cacheAsBitmap` property to `true`. Removing all filters restores the original `cacheAsBitmap` value. - + This filter supports Stage scaling but not general scaling, rotation, or skewing. If the object itself is scaled (`scaleX` and `scaleY` ≠ 100%), the filter effect doesn't scale—it only scales when the Stage is zoomed. - + A filter won't be applied if the resulting image exceeds maximum dimensions: * AIR 1.5/Flash Player 10+: 8,191px width/height, 16,777,215 total pixels * Flash Player 9/AIR 1.1-: 2,880px width/height - + For example, zooming into a large filtered movie clip may disable the filter if the resulting image exceeds these limits. **/ @:access(openfl.geom.Point) @:access(openfl.geom.Rectangle) -@:final class BloomEffect extends BitmapFilter +class BloomEffect extends BitmapFilter { @:noCompletion private static var __blurShader:BlurShader = new BlurShader(); @:noCompletion private static var __combineShader:CombineShader = new CombineShader(); @@ -153,7 +153,8 @@ import openfl.geom.Rectangle; @param useLowQualityExtract Enables performance-optimized extraction with potentially more flickering. **/ - public function new(blurX:Float = 10, blurY:Float = 10, quality:Float = #if desktop 24 #else 8 #end, strength:Float = 1, threshold:Float = 0.6, useLowQualityExtract:Bool = #if desktop false #else true #end) + public function new(blurX:Float = 10, blurY:Float = 10, quality:Float = #if desktop 24 #else 8 #end, strength:Float = 1, threshold:Float = 0.6, + useLowQualityExtract:Bool = #if desktop false #else true #end) { super(); @@ -474,7 +475,7 @@ private class ExtractLowShader extends BitmapFilterShader if ((all(greaterThanEqual(vTexCoord, vec2(0.0))) && all(lessThanEqual(vTexCoord, vec2(1.0)))) == false) return; vec4 texel = texture2D(openfl_Texture, vTexCoord); - float brightness = max(max(texel.r, texel.g), texel.b); + float brightness = dot(texel.rgb, vec3(0.2126, 0.7152, 0.0722)); float mask = smoothstep(uThreshold, uThreshold + 0.1, brightness); gl_FragColor = texel * mask; } @@ -512,9 +513,10 @@ private class ExtractShader extends BitmapFilterShader uniform float uThreshold; uniform float uQuality; varying vec2 vTexCoord; + varying vec4 border; void main(void) { - if ((all(greaterThanEqual(vTexCoord, vec2(0.0))) && all(lessThanEqual(vTexCoord, vec2(1.0)))) == false) return; + if ((all(greaterThanEqual(vTexCoord, border.xy)) && all(lessThanEqual(vTexCoord, border.zw))) == false) return; float quality = floor(uQuality) / 2.0; vec2 texelSize = 1.0 / openfl_TextureSize; @@ -527,10 +529,8 @@ private class ExtractShader extends BitmapFilterShader for (float dy = -quality; dy <= quality; dy += 2.0) { vec2 sampleCoord = vTexCoord + vec2(dx, dy) * texelSize; - if ((all(greaterThanEqual(sampleCoord, vec2(0.0))) && all(lessThanEqual(sampleCoord, vec2(1.0)))) == false) continue; - vec4 texel = texture2D(openfl_Texture, sampleCoord); - float brightness = max(max(texel.r, texel.g), texel.b); + float brightness = dot(texel.rgb, vec3(0.2126, 0.7152, 0.0722)); float mask = smoothstep(uThreshold, uThreshold + 0.1, brightness); accumulated += texel * mask; sampleCount++; @@ -547,10 +547,15 @@ private class ExtractShader extends BitmapFilterShader uniform vec2 openfl_TextureSize; uniform float uQuality; varying vec2 vTexCoord; + varying vec4 border; void main(void) { vec4 pos = openfl_Position; pos.xy /= uQuality; + + vec2 size = 1.0 / openfl_TextureSize * uQuality; + border = vec4(size, vec2(1.0) - size); + gl_Position = openfl_Matrix * pos; vTexCoord = openfl_TextureCoord; }