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/20] 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 0000000000..8084e46bd7 --- /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/20] 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 8084e46bd7..011f81aa3d 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/20] 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 011f81aa3d..0200607da7 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/20] 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 0200607da7..b391246ede 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/20] 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 b391246ede..bba6a043b8 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/20] 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 bba6a043b8..9bd4822a77 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/20] 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 9bd4822a77..abdeceff58 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/20] 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 abdeceff58..c09fbd88f9 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/20] 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 c09fbd88f9..4b49c59b1c 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/20] 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 4b49c59b1c..5877992c2e 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/20] 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 5877992c2e..42729ce555 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/20] 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 42729ce555..78840a626d 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/20] 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 78840a626d..713a808390 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; } From 9be588cb1ba6b63ff081384613118344525e4dbe Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Mon, 2 Mar 2026 19:31:28 +0800 Subject: [PATCH 14/20] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20BloomEffect.hx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/funkin/backend/shaders/BloomEffect.hx | 85 +++++++++++++++----- 1 file changed, 64 insertions(+), 21 deletions(-) diff --git a/source/funkin/backend/shaders/BloomEffect.hx b/source/funkin/backend/shaders/BloomEffect.hx index 713a808390..237d497b51 100644 --- a/source/funkin/backend/shaders/BloomEffect.hx +++ b/source/funkin/backend/shaders/BloomEffect.hx @@ -52,10 +52,15 @@ import openfl.geom.Rectangle; @:access(openfl.geom.Rectangle) 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(); - @:noCompletion private static var __extractLowShader:ExtractLowShader = new ExtractLowShader(); + /** + [Warning] + You can redefine these shaders, but it is not recommended to modify them without + understanding how they work. You can use CustomShader/FunkinShader to redefine them. + **/ + public var blurShader:BlurShader; + public var combineShader:CombineShader; + public var extractShader:ExtractShader; + public var extractLowShader:ExtractLowShader; /** Values that are a power of 2 (such as 2, 4, 8, 16 and 32) are optimized to render @@ -99,6 +104,12 @@ class BloomEffect extends BitmapFilter **/ public var useLowQualityExtract(get, set):Bool; + /** + The weights for calculating brightness (RGB to grayscale). + Order: [Red, Green, Blue]. Default is [0.2126, 0.7152, 0.0722]. + **/ + public var weights(get, set):Array; + @:noCompletion private var __blurX:Float; @:noCompletion private var __blurY:Float; @:noCompletion private var __horizontalPasses:Int; @@ -108,6 +119,7 @@ class BloomEffect extends BitmapFilter @:noCompletion private var __threshold:Float; @:noCompletion private var __extension:Bool; @:noCompletion private var __useLowQualityExtract:Bool; + @:noCompletion private var __weights:Array; #if openfljs @:noCompletion private static function __init__() @@ -137,6 +149,10 @@ class BloomEffect extends BitmapFilter 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); }") }, + "weights": { + get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_weights (); }"), + set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_weights (v); }") + }, }); } #end @@ -158,6 +174,11 @@ class BloomEffect extends BitmapFilter { super(); + blurShader = new BlurShader(); + combineShader = new CombineShader(); + extractShader = new ExtractShader(); + extractLowShader = new ExtractLowShader(); + this.blurX = blurX; this.blurY = blurY; this.quality = quality; @@ -165,6 +186,7 @@ class BloomEffect extends BitmapFilter this.threshold = threshold; this.extension = false; this.useLowQualityExtract = useLowQualityExtract; + this.weights = [0.2126, 0.7152, 0.0722]; __needSecondBitmapData = true; __preserveObject = true; @@ -192,15 +214,17 @@ class BloomEffect extends BitmapFilter case 0: if (__useLowQualityExtract) { - __extractLowShader.uThreshold.value = [__threshold]; - __extractLowShader.uQuality.value = [__quality]; - return __extractLowShader; + extractLowShader.uThreshold.value = [__threshold]; + extractLowShader.uQuality.value = [__quality]; + extractLowShader.uWeights.value = __weights; + return extractLowShader; } else { - __extractShader.uThreshold.value = [__threshold]; - __extractShader.uQuality.value = [__quality]; - return __extractShader; + extractShader.uThreshold.value = [__threshold]; + extractShader.uQuality.value = [__quality]; + extractShader.uWeights.value = __weights; + return extractShader; } case _ if (pass <= numBlurPasses): @@ -212,18 +236,18 @@ class BloomEffect extends BitmapFilter final scale = Math.pow(0.5, scalePass >> 1); final blurRadius = isHorizontal ? blurX * scale : blurY * scale; - __blurShader.uRadius.value = isHorizontal ? [blurRadius / __quality, 0.0] : [0.0, blurRadius / __quality]; - __blurShader.uQuality.value = [__quality]; + blurShader.uRadius.value = isHorizontal ? [blurRadius / __quality, 0.0] : [0.0, blurRadius / __quality]; + blurShader.uQuality.value = [__quality]; - return __blurShader; + return blurShader; default: - __combineShader.sourceBitmap.input = sourceBitmapData; - __combineShader.offset.value = [0.0, 0.0]; - __combineShader.uStrength.value = [__strength]; - __combineShader.uThreshold.value = [__threshold]; - __combineShader.uQuality.value = [__quality]; - return __combineShader; + combineShader.sourceBitmap.input = sourceBitmapData; + combineShader.offset.value = [0.0, 0.0]; + combineShader.uStrength.value = [__strength]; + combineShader.uThreshold.value = [__threshold]; + combineShader.uQuality.value = [__quality]; + return combineShader; } #else return null; @@ -383,6 +407,21 @@ class BloomEffect extends BitmapFilter } return value; } + + @:noCompletion private function get_weights():Array + { + return __weights; + } + + @:noCompletion private function set_weights(value:Array):Array + { + if (value != __weights) + { + __weights = value; + __renderDirty = true; + } + return value; + } } private class BlurShader extends BitmapFilterShader @@ -469,13 +508,14 @@ private class ExtractLowShader extends BitmapFilterShader @:glFragmentSource(" uniform sampler2D openfl_Texture; uniform float uThreshold; + uniform vec3 uWeights; 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 = dot(texel.rgb, vec3(0.2126, 0.7152, 0.0722)); + float brightness = min(dot(texel.rgb, uWeights), 1.0); float mask = smoothstep(uThreshold, uThreshold + 0.1, brightness); gl_FragColor = texel * mask; } @@ -501,6 +541,7 @@ private class ExtractLowShader extends BitmapFilterShader #if !macro uThreshold.value = [0.6]; + uWeights.value = [0.2126, 0.7152, 0.0722]; #end } } @@ -512,6 +553,7 @@ private class ExtractShader extends BitmapFilterShader uniform vec2 openfl_TextureSize; uniform float uThreshold; uniform float uQuality; + uniform vec3 uWeights; varying vec2 vTexCoord; varying vec4 border; @@ -530,7 +572,7 @@ private class ExtractShader extends BitmapFilterShader vec2 sampleCoord = vTexCoord + vec2(dx, dy) * texelSize; vec4 texel = texture2D(openfl_Texture, sampleCoord); - float brightness = dot(texel.rgb, vec3(0.2126, 0.7152, 0.0722)); + float brightness = min(dot(texel.rgb, uWeights), 1.0); float mask = smoothstep(uThreshold, uThreshold + 0.1, brightness); accumulated += texel * mask; sampleCount++; @@ -566,6 +608,7 @@ private class ExtractShader extends BitmapFilterShader #if !macro uThreshold.value = [0.6]; + uWeights.value = [0.2126, 0.7152, 0.0722]; #end } } From b6c5f7f0f225f88cefc1ca8cc34cb90149a47cbe Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Mon, 2 Mar 2026 21:05:33 +0800 Subject: [PATCH 15/20] blendMode Fixed memory growth Added blendMode --- source/funkin/backend/shaders/BloomEffect.hx | 365 +++++++++++++++++-- 1 file changed, 335 insertions(+), 30 deletions(-) diff --git a/source/funkin/backend/shaders/BloomEffect.hx b/source/funkin/backend/shaders/BloomEffect.hx index 237d497b51..676f90632d 100644 --- a/source/funkin/backend/shaders/BloomEffect.hx +++ b/source/funkin/backend/shaders/BloomEffect.hx @@ -5,6 +5,7 @@ import openfl.filters.BitmapFilter; import openfl.filters.BitmapFilterShader; import openfl.display.BitmapData; import openfl.display.DisplayObjectRenderer; +import openfl.display.BlendMode; import openfl.display.Shader; import openfl.geom.Point; import openfl.geom.Rectangle; @@ -56,11 +57,20 @@ class BloomEffect extends BitmapFilter [Warning] You can redefine these shaders, but it is not recommended to modify them without understanding how they work. You can use CustomShader/FunkinShader to redefine them. + Use setShader() to replace a shader with a custom one. **/ - public var blurShader:BlurShader; - public var combineShader:CombineShader; - public var extractShader:ExtractShader; - public var extractLowShader:ExtractLowShader; + @:noCompletion private static var __shaderCache:Map = new Map(); + // I have no idea why, but not using static variable caching causes memory to skyrocket. + + @:noCompletion private var __blurShader:BlurShader; + @:noCompletion private var __combineShader:CombineShader; + @:noCompletion private var __extractShader:ExtractShader; + @:noCompletion private var __extractLowShader:ExtractLowShader; + + public var blurShader(get, never):BlurShader; + public var combineShader(get, never):CombineShader; + public var extractShader(get, never):ExtractShader; + public var extractLowShader(get, never):ExtractLowShader; /** Values that are a power of 2 (such as 2, 4, 8, 16 and 32) are optimized to render @@ -110,6 +120,13 @@ class BloomEffect extends BitmapFilter **/ public var weights(get, set):Array; + /** + The blend mode used when combining the bloom with the original image. + Use BlendMode constants (e.g., BlendMode.ADD, BlendMode.SCREEN, BlendMode.ADD). + Default is BlendMode.ADD. + **/ + public var blendMode(get, set):BlendMode; + @:noCompletion private var __blurX:Float; @:noCompletion private var __blurY:Float; @:noCompletion private var __horizontalPasses:Int; @@ -120,6 +137,7 @@ class BloomEffect extends BitmapFilter @:noCompletion private var __extension:Bool; @:noCompletion private var __useLowQualityExtract:Bool; @:noCompletion private var __weights:Array; + @:noCompletion private var __blendMode:BlendMode; #if openfljs @:noCompletion private static function __init__() @@ -153,6 +171,10 @@ class BloomEffect extends BitmapFilter get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_weights (); }"), set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_weights (v); }") }, + "blendMode": { + get: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function () { return this.get_blendMode (); }"), + set: untyped #if haxe4 js.Syntax.code #else __js__ #end ("function (v) { return this.set_blendMode (v); }") + }, }); } #end @@ -169,15 +191,19 @@ class BloomEffect extends BitmapFilter @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 = 8, strength:Float = 1, threshold:Float = 0.6, useLowQualityExtract:Bool = true) { super(); - blurShader = new BlurShader(); - combineShader = new CombineShader(); - extractShader = new ExtractShader(); - extractLowShader = new ExtractLowShader(); + if (!__shaderCache.exists("blur")) __shaderCache.set("blur", new BlurShader()); + if (!__shaderCache.exists("combine")) __shaderCache.set("combine", new CombineShader()); + if (!__shaderCache.exists("extract")) __shaderCache.set("extract", new ExtractShader()); + if (!__shaderCache.exists("extractLow")) __shaderCache.set("extractLow", new ExtractLowShader()); + + __blurShader = __shaderCache.get("blur"); + __combineShader = __shaderCache.get("combine"); + __extractShader = __shaderCache.get("extract"); + __extractLowShader = __shaderCache.get("extractLow"); this.blurX = blurX; this.blurY = blurY; @@ -187,6 +213,7 @@ class BloomEffect extends BitmapFilter this.extension = false; this.useLowQualityExtract = useLowQualityExtract; this.weights = [0.2126, 0.7152, 0.0722]; + this.blendMode = BlendMode.ADD; __needSecondBitmapData = true; __preserveObject = true; @@ -195,7 +222,10 @@ class BloomEffect extends BitmapFilter public override function clone():BitmapFilter { - return new BloomEffect(__blurX, __blurY, __quality, __strength, __threshold, __useLowQualityExtract); + var cloned = new BloomEffect(__blurX, __blurY, __quality, __strength, __threshold, __useLowQualityExtract); + cloned.weights = __weights != null ? __weights.copy() : [0.2126, 0.7152, 0.0722]; + cloned.blendMode = __blendMode; + return cloned; } @:noCompletion private override function __applyFilter(bitmapData:BitmapData, sourceBitmapData:BitmapData, sourceRect:Rectangle, destPoint:Point):BitmapData @@ -214,17 +244,17 @@ class BloomEffect extends BitmapFilter case 0: if (__useLowQualityExtract) { - extractLowShader.uThreshold.value = [__threshold]; - extractLowShader.uQuality.value = [__quality]; - extractLowShader.uWeights.value = __weights; - return extractLowShader; + __extractLowShader.uThreshold.value = [__threshold]; + __extractLowShader.uQuality.value = [__quality]; + __extractLowShader.uWeights.value = __weights; + return __extractLowShader; } else { - extractShader.uThreshold.value = [__threshold]; - extractShader.uQuality.value = [__quality]; - extractShader.uWeights.value = __weights; - return extractShader; + __extractShader.uThreshold.value = [__threshold]; + __extractShader.uQuality.value = [__quality]; + __extractShader.uWeights.value = __weights; + return __extractShader; } case _ if (pass <= numBlurPasses): @@ -236,18 +266,19 @@ class BloomEffect extends BitmapFilter final scale = Math.pow(0.5, scalePass >> 1); final blurRadius = isHorizontal ? blurX * scale : blurY * scale; - blurShader.uRadius.value = isHorizontal ? [blurRadius / __quality, 0.0] : [0.0, blurRadius / __quality]; - blurShader.uQuality.value = [__quality]; + __blurShader.uRadius.value = isHorizontal ? [blurRadius / __quality, 0.0] : [0.0, blurRadius / __quality]; + __blurShader.uQuality.value = [__quality]; - return blurShader; + return __blurShader; default: - combineShader.sourceBitmap.input = sourceBitmapData; - combineShader.offset.value = [0.0, 0.0]; - combineShader.uStrength.value = [__strength]; - combineShader.uThreshold.value = [__threshold]; - combineShader.uQuality.value = [__quality]; - return combineShader; + __combineShader.sourceBitmap.input = sourceBitmapData; + __combineShader.offset.value = [0.0, 0.0]; + __combineShader.uStrength.value = [__strength]; + __combineShader.uThreshold.value = [__threshold]; + __combineShader.uQuality.value = [__quality]; + __combineShader.uBlendMode.value = [cast __blendMode]; + return __combineShader; } #else return null; @@ -422,6 +453,70 @@ class BloomEffect extends BitmapFilter } return value; } + + @:noCompletion private function get_blendMode():BlendMode + { + return __blendMode; + } + + @:noCompletion private function set_blendMode(value:BlendMode):BlendMode + { + if (value != __blendMode) + { + __blendMode = value; + __renderDirty = true; + } + return value; + } + + @:noCompletion private function get_blurShader():BlurShader + { + return __blurShader; + } + + @:noCompletion private function get_combineShader():CombineShader + { + return __combineShader; + } + + @:noCompletion private function get_extractShader():ExtractShader + { + return __extractShader; + } + + @:noCompletion private function get_extractLowShader():ExtractLowShader + { + return __extractLowShader; + } + + /** + Replaces a shader with a custom one. + + @param type The shader type to replace. Valid values: "blur", "combine", "extract", "extractLow" + @param shader The new shader instance to use + + Usage: + ```haxe + var bloom = new BloomEffect(); + bloom.setShader("combine", new MyCustomCombineShader()); + bloom.setShader("blur", new MyCustomBlurShader()); + ``` + **/ + public function setShader(type:String, shader:Dynamic):Void + { + switch (type) + { + case "blur": + __blurShader = cast shader; + case "combine": + __combineShader = cast shader; + case "extract": + __extractShader = cast shader; + case "extractLow": + __extractLowShader = cast shader; + } + __renderDirty = true; + } } private class BlurShader extends BitmapFilterShader @@ -620,13 +715,222 @@ private class CombineShader extends BitmapFilterShader uniform sampler2D sourceBitmap; uniform float uStrength; uniform float uThreshold; + uniform int uBlendMode; varying vec4 textureCoords; + vec4 blendAdd(vec4 src, vec4 bloom) { + return src + bloom; + } + + vec4 blendScreen(vec4 src, vec4 bloom) { + return vec4(1.0) - (vec4(1.0) - src) * (vec4(1.0) - bloom); + } + + vec4 blendMultiply(vec4 src, vec4 bloom) { + return src * bloom; + } + + vec4 blendLighten(vec4 src, vec4 bloom) { + return max(src, bloom); + } + + vec4 blendDarken(vec4 src, vec4 bloom) { + return min(src, bloom); + } + + vec4 blendOverlay(vec4 src, vec4 bloom) { + vec4 result = vec4(0.0); + for(int i = 0; i < 4; i++) { + if(src[i] < 0.5) { + result[i] = 2.0 * src[i] * bloom[i]; + } else { + result[i] = 1.0 - 2.0 * (1.0 - src[i]) * (1.0 - bloom[i]); + } + } + return result; + } + + vec4 blendColorDodge(vec4 src, vec4 bloom) { + vec4 result = vec4(0.0); + for(int i = 0; i < 4; i++) { + if(bloom[i] < 1.0) { + result[i] = min(1.0, src[i] / (1.0 - bloom[i])); + } else { + result[i] = 1.0; + } + } + return result; + } + + vec4 blendColorBurn(vec4 src, vec4 bloom) { + vec4 result = vec4(0.0); + for(int i = 0; i < 4; i++) { + if(bloom[i] > 0.0) { + result[i] = max(0.0, 1.0 - (1.0 - src[i]) / bloom[i]); + } else { + result[i] = 0.0; + } + } + return result; + } + + vec4 blendSoftLight(vec4 src, vec4 bloom) { + vec4 result = vec4(0.0); + for(int i = 0; i < 4; i++) { + if(bloom[i] < 0.5) { + result[i] = src[i] - (1.0 - 2.0 * bloom[i]) * src[i] * (1.0 - src[i]); + } else { + float d = (src[i] <= 0.25) ? ((16.0 * src[i] - 12.0) * src[i] + 4.0) * src[i] : sqrt(src[i]); + result[i] = src[i] + (2.0 * bloom[i] - 1.0) * (d - src[i]); + } + } + return result; + } + + vec4 blendHardLight(vec4 src, vec4 bloom) { + return blendOverlay(bloom, src); + } + + vec4 blendDifference(vec4 src, vec4 bloom) { + return abs(src - bloom); + } + + vec4 blendExclusion(vec4 src, vec4 bloom) { + return src + bloom - 2.0 * src * bloom; + } + + vec4 blendAlpha(vec4 src, vec4 bloom) { + return src + bloom * (1.0 - src.a); + } + + vec3 rgb2hsl(vec3 color) { + float maxC = max(max(color.r, color.g), color.b); + float minC = min(min(color.r, color.g), color.b); + float l = (maxC + minC) / 2.0; + float h = 0.0; + float s = 0.0; + if(maxC != minC) { + float d = maxC - minC; + s = l > 0.5 ? d / (2.0 - maxC - minC) : d / (maxC + minC); + if(maxC == color.r) { + h = (color.g - color.b) / d + (color.g < color.b ? 6.0 : 0.0); + } else if(maxC == color.g) { + h = (color.b - color.r) / d + 2.0; + } else { + h = (color.r - color.g) / d + 4.0; + } + h /= 6.0; + } + return vec3(h, s, l); + } + + float hue2rgb(float p, float q, float t) { + if(t < 0.0) t += 1.0; + if(t > 1.0) t -= 1.0; + if(t < 1.0/6.0) return p + (q - p) * 6.0 * t; + if(t < 1.0/2.0) return q; + if(t < 2.0/3.0) return p + (q - p) * (2.0/3.0 - t) * 6.0; + return p; + } + + vec3 hsl2rgb(vec3 hsl) { + float r, g, b; + if(hsl.y == 0.0) { + r = g = b = hsl.z; + } else { + float q = hsl.z < 0.5 ? hsl.z * (1.0 + hsl.y) : hsl.z + hsl.y - hsl.z * hsl.y; + float p = 2.0 * hsl.z - q; + r = hue2rgb(p, q, hsl.x + 1.0/3.0); + g = hue2rgb(p, q, hsl.x); + b = hue2rgb(p, q, hsl.x - 1.0/3.0); + } + return vec3(r, g, b); + } + + vec4 blendHue(vec4 src, vec4 bloom) { + vec3 srcHSL = rgb2hsl(src.rgb); + vec3 bloomHSL = rgb2hsl(bloom.rgb); + vec3 resultRGB = hsl2rgb(vec3(bloomHSL.x, srcHSL.y, srcHSL.z)); + return vec4(resultRGB, src.a); + } + + vec4 blendSaturation(vec4 src, vec4 bloom) { + vec3 srcHSL = rgb2hsl(src.rgb); + vec3 bloomHSL = rgb2hsl(bloom.rgb); + vec3 resultRGB = hsl2rgb(vec3(srcHSL.x, bloomHSL.y, srcHSL.z)); + return vec4(resultRGB, src.a); + } + + vec4 blendColor(vec4 src, vec4 bloom) { + vec3 srcHSL = rgb2hsl(src.rgb); + vec3 bloomHSL = rgb2hsl(bloom.rgb); + vec3 resultRGB = hsl2rgb(vec3(bloomHSL.x, bloomHSL.y, srcHSL.z)); + return vec4(resultRGB, src.a); + } + + vec4 blendLuminosity(vec4 src, vec4 bloom) { + vec3 srcHSL = rgb2hsl(src.rgb); + vec3 bloomHSL = rgb2hsl(bloom.rgb); + vec3 resultRGB = hsl2rgb(vec3(srcHSL.x, srcHSL.y, bloomHSL.z)); + return vec4(resultRGB, src.a); + } + void main(void) { vec4 src = texture2D(sourceBitmap, textureCoords.xy); - vec4 bloom = texture2D(openfl_Texture, textureCoords.zw); + vec4 bloom = texture2D(openfl_Texture, textureCoords.zw) * uStrength; + + vec4 result; + if(uBlendMode == 0) { + result = src + bloom; + } else if(uBlendMode == 1) { + result = blendAlpha(src, bloom); + } else if(uBlendMode == 2) { + result = blendDarken(src, bloom); + } else if(uBlendMode == 3) { + result = blendDifference(src, bloom); + } else if(uBlendMode == 4) { + result = vec4(src.rgb, src.a - bloom.a); + } else if(uBlendMode == 5) { + result = blendHardLight(src, bloom); + } else if(uBlendMode == 6) { + result = vec4(1.0) - src; + } else if(uBlendMode == 7) { + result = src; + } else if(uBlendMode == 8) { + result = blendLighten(src, bloom); + } else if(uBlendMode == 9) { + result = blendMultiply(src, bloom); + } else if(uBlendMode == 10) { + result = bloom; + } else if(uBlendMode == 11) { + result = blendOverlay(src, bloom); + } else if(uBlendMode == 12) { + result = blendScreen(src, bloom); + } else if(uBlendMode == 13) { + result = src + bloom; + } else if(uBlendMode == 14) { + result = src + bloom - 1.0; + } else if(uBlendMode == 15) { + result = blendColorDodge(src, bloom); + } else if(uBlendMode == 16) { + result = blendColorBurn(src, bloom); + } else if(uBlendMode == 17) { + result = blendSoftLight(src, bloom); + } else if(uBlendMode == 18) { + result = blendExclusion(src, bloom); + } else if(uBlendMode == 19) { + result = blendHue(src, bloom); + } else if(uBlendMode == 20) { + result = blendSaturation(src, bloom); + } else if(uBlendMode == 21) { + result = blendColor(src, bloom); + } else if(uBlendMode == 22) { + result = blendLuminosity(src, bloom); + } else { + result = src + bloom; + } - gl_FragColor = src + bloom * uStrength; + gl_FragColor = result; } ") @:glVertexSource("attribute vec4 openfl_Position; @@ -650,6 +954,7 @@ private class CombineShader extends BitmapFilterShader offset.value = [0, 0]; uStrength.value = [1.0]; uThreshold.value = [0.6]; + uBlendMode.value = [0]; #end } } \ No newline at end of file From 662db9dd3909408b0e957d546bab8175a646feba Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Tue, 3 Mar 2026 01:55:01 +0800 Subject: [PATCH 16/20] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20BloomEffect.hx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 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 676f90632d..da29ccf68b 100644 --- a/source/funkin/backend/shaders/BloomEffect.hx +++ b/source/funkin/backend/shaders/BloomEffect.hx @@ -122,7 +122,7 @@ class BloomEffect extends BitmapFilter /** The blend mode used when combining the bloom with the original image. - Use BlendMode constants (e.g., BlendMode.ADD, BlendMode.SCREEN, BlendMode.ADD). + Use BlendMode constants (e.g., BlendMode.ADD, BlendMode.SCREEN). Default is BlendMode.ADD. **/ public var blendMode(get, set):BlendMode; From 4ea3f7fb618899dce291756ddde95727c0c95fbc Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Tue, 3 Mar 2026 22:49:17 +0800 Subject: [PATCH 17/20] Wrap-up --- source/funkin/backend/shaders/BloomEffect.hx | 207 +++---------------- 1 file changed, 26 insertions(+), 181 deletions(-) diff --git a/source/funkin/backend/shaders/BloomEffect.hx b/source/funkin/backend/shaders/BloomEffect.hx index da29ccf68b..751fd6b39b 100644 --- a/source/funkin/backend/shaders/BloomEffect.hx +++ b/source/funkin/backend/shaders/BloomEffect.hx @@ -11,6 +11,7 @@ import openfl.geom.Point; import openfl.geom.Rectangle; /** + This BloomEffect was modified by heihua based on openfl.filters.BlurFilter. 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 @@ -21,33 +22,6 @@ import openfl.geom.Rectangle; 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) @@ -122,7 +96,9 @@ class BloomEffect extends BitmapFilter /** The blend mode used when combining the bloom with the original image. - Use BlendMode constants (e.g., BlendMode.ADD, BlendMode.SCREEN). + BlendMode currently supports: (BlendMode.ADD, BlendMode.ALPHA, BlendMode.HARDLIGHT, + BlendMode.LIGHTEN, BlendMode.MULTIPLY, BlendMode.OVERLAY, BlendMode.SCREEN, + BlendMode.COLORDODGE, BlendMode.SOFTLIGHT). Default is BlendMode.ADD. **/ public var blendMode(get, set):BlendMode; @@ -191,7 +167,7 @@ class BloomEffect extends BitmapFilter @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, useLowQualityExtract:Bool = true) + public function new(blurX:Float = 50, blurY:Float = 50, quality:Float = 8, strength:Float = 0.6, threshold:Float = 0.6, useLowQualityExtract:Bool = true) { super(); @@ -244,15 +220,15 @@ class BloomEffect extends BitmapFilter case 0: if (__useLowQualityExtract) { - __extractLowShader.uThreshold.value = [__threshold]; - __extractLowShader.uQuality.value = [__quality]; + __extractLowShader.uThreshold.value[0] = __threshold; + __extractLowShader.uQuality.value[0] = __quality; __extractLowShader.uWeights.value = __weights; return __extractLowShader; } else { - __extractShader.uThreshold.value = [__threshold]; - __extractShader.uQuality.value = [__quality]; + __extractShader.uThreshold.value[0] = __threshold; + __extractShader.uQuality.value[0] = __quality; __extractShader.uWeights.value = __weights; return __extractShader; } @@ -267,17 +243,16 @@ class BloomEffect extends BitmapFilter final blurRadius = isHorizontal ? blurX * scale : blurY * scale; __blurShader.uRadius.value = isHorizontal ? [blurRadius / __quality, 0.0] : [0.0, blurRadius / __quality]; - __blurShader.uQuality.value = [__quality]; + __blurShader.uQuality.value[0] = __quality; return __blurShader; default: __combineShader.sourceBitmap.input = sourceBitmapData; - __combineShader.offset.value = [0.0, 0.0]; - __combineShader.uStrength.value = [__strength]; - __combineShader.uThreshold.value = [__threshold]; - __combineShader.uQuality.value = [__quality]; - __combineShader.uBlendMode.value = [cast __blendMode]; + __combineShader.uStrength.value[0] = __strength; + __combineShader.uThreshold.value[0] = __threshold; + __combineShader.uQuality.value[0] = __quality; + __combineShader.uBlendMode.value[0] = cast __blendMode; return __combineShader; } #else @@ -585,6 +560,7 @@ private class BlurShader extends BitmapFilterShader #if !macro uRadius.value = [0, 0]; + uQuality.value = [8]; #end } @@ -636,6 +612,7 @@ private class ExtractLowShader extends BitmapFilterShader #if !macro uThreshold.value = [0.6]; + uQuality.value = [8]; uWeights.value = [0.2126, 0.7152, 0.0722]; #end } @@ -703,6 +680,7 @@ private class ExtractShader extends BitmapFilterShader #if !macro uThreshold.value = [0.6]; + uQuality.value = [8]; uWeights.value = [0.2126, 0.7152, 0.0722]; #end } @@ -718,10 +696,6 @@ private class CombineShader extends BitmapFilterShader uniform int uBlendMode; varying vec4 textureCoords; - vec4 blendAdd(vec4 src, vec4 bloom) { - return src + bloom; - } - vec4 blendScreen(vec4 src, vec4 bloom) { return vec4(1.0) - (vec4(1.0) - src) * (vec4(1.0) - bloom); } @@ -734,14 +708,10 @@ private class CombineShader extends BitmapFilterShader return max(src, bloom); } - vec4 blendDarken(vec4 src, vec4 bloom) { - return min(src, bloom); - } - vec4 blendOverlay(vec4 src, vec4 bloom) { vec4 result = vec4(0.0); - for(int i = 0; i < 4; i++) { - if(src[i] < 0.5) { + for (int i = 0; i < 4; i++) { + if (src[i] < 0.5) { result[i] = 2.0 * src[i] * bloom[i]; } else { result[i] = 1.0 - 2.0 * (1.0 - src[i]) * (1.0 - bloom[i]); @@ -752,8 +722,8 @@ private class CombineShader extends BitmapFilterShader vec4 blendColorDodge(vec4 src, vec4 bloom) { vec4 result = vec4(0.0); - for(int i = 0; i < 4; i++) { - if(bloom[i] < 1.0) { + for (int i = 0; i < 4; i++) { + if (bloom[i] < 1.0) { result[i] = min(1.0, src[i] / (1.0 - bloom[i])); } else { result[i] = 1.0; @@ -762,22 +732,10 @@ private class CombineShader extends BitmapFilterShader return result; } - vec4 blendColorBurn(vec4 src, vec4 bloom) { - vec4 result = vec4(0.0); - for(int i = 0; i < 4; i++) { - if(bloom[i] > 0.0) { - result[i] = max(0.0, 1.0 - (1.0 - src[i]) / bloom[i]); - } else { - result[i] = 0.0; - } - } - return result; - } - vec4 blendSoftLight(vec4 src, vec4 bloom) { vec4 result = vec4(0.0); - for(int i = 0; i < 4; i++) { - if(bloom[i] < 0.5) { + for (int i = 0; i < 4; i++) { + if (bloom[i] < 0.5) { result[i] = src[i] - (1.0 - 2.0 * bloom[i]) * src[i] * (1.0 - src[i]); } else { float d = (src[i] <= 0.25) ? ((16.0 * src[i] - 12.0) * src[i] + 4.0) * src[i] : sqrt(src[i]); @@ -787,94 +745,10 @@ private class CombineShader extends BitmapFilterShader return result; } - vec4 blendHardLight(vec4 src, vec4 bloom) { - return blendOverlay(bloom, src); - } - - vec4 blendDifference(vec4 src, vec4 bloom) { - return abs(src - bloom); - } - - vec4 blendExclusion(vec4 src, vec4 bloom) { - return src + bloom - 2.0 * src * bloom; - } - vec4 blendAlpha(vec4 src, vec4 bloom) { return src + bloom * (1.0 - src.a); } - vec3 rgb2hsl(vec3 color) { - float maxC = max(max(color.r, color.g), color.b); - float minC = min(min(color.r, color.g), color.b); - float l = (maxC + minC) / 2.0; - float h = 0.0; - float s = 0.0; - if(maxC != minC) { - float d = maxC - minC; - s = l > 0.5 ? d / (2.0 - maxC - minC) : d / (maxC + minC); - if(maxC == color.r) { - h = (color.g - color.b) / d + (color.g < color.b ? 6.0 : 0.0); - } else if(maxC == color.g) { - h = (color.b - color.r) / d + 2.0; - } else { - h = (color.r - color.g) / d + 4.0; - } - h /= 6.0; - } - return vec3(h, s, l); - } - - float hue2rgb(float p, float q, float t) { - if(t < 0.0) t += 1.0; - if(t > 1.0) t -= 1.0; - if(t < 1.0/6.0) return p + (q - p) * 6.0 * t; - if(t < 1.0/2.0) return q; - if(t < 2.0/3.0) return p + (q - p) * (2.0/3.0 - t) * 6.0; - return p; - } - - vec3 hsl2rgb(vec3 hsl) { - float r, g, b; - if(hsl.y == 0.0) { - r = g = b = hsl.z; - } else { - float q = hsl.z < 0.5 ? hsl.z * (1.0 + hsl.y) : hsl.z + hsl.y - hsl.z * hsl.y; - float p = 2.0 * hsl.z - q; - r = hue2rgb(p, q, hsl.x + 1.0/3.0); - g = hue2rgb(p, q, hsl.x); - b = hue2rgb(p, q, hsl.x - 1.0/3.0); - } - return vec3(r, g, b); - } - - vec4 blendHue(vec4 src, vec4 bloom) { - vec3 srcHSL = rgb2hsl(src.rgb); - vec3 bloomHSL = rgb2hsl(bloom.rgb); - vec3 resultRGB = hsl2rgb(vec3(bloomHSL.x, srcHSL.y, srcHSL.z)); - return vec4(resultRGB, src.a); - } - - vec4 blendSaturation(vec4 src, vec4 bloom) { - vec3 srcHSL = rgb2hsl(src.rgb); - vec3 bloomHSL = rgb2hsl(bloom.rgb); - vec3 resultRGB = hsl2rgb(vec3(srcHSL.x, bloomHSL.y, srcHSL.z)); - return vec4(resultRGB, src.a); - } - - vec4 blendColor(vec4 src, vec4 bloom) { - vec3 srcHSL = rgb2hsl(src.rgb); - vec3 bloomHSL = rgb2hsl(bloom.rgb); - vec3 resultRGB = hsl2rgb(vec3(bloomHSL.x, bloomHSL.y, srcHSL.z)); - return vec4(resultRGB, src.a); - } - - vec4 blendLuminosity(vec4 src, vec4 bloom) { - vec3 srcHSL = rgb2hsl(src.rgb); - vec3 bloomHSL = rgb2hsl(bloom.rgb); - vec3 resultRGB = hsl2rgb(vec3(srcHSL.x, srcHSL.y, bloomHSL.z)); - return vec4(resultRGB, src.a); - } - void main(void) { vec4 src = texture2D(sourceBitmap, textureCoords.xy); vec4 bloom = texture2D(openfl_Texture, textureCoords.zw) * uStrength; @@ -884,48 +758,20 @@ private class CombineShader extends BitmapFilterShader result = src + bloom; } else if(uBlendMode == 1) { result = blendAlpha(src, bloom); - } else if(uBlendMode == 2) { - result = blendDarken(src, bloom); - } else if(uBlendMode == 3) { - result = blendDifference(src, bloom); - } else if(uBlendMode == 4) { - result = vec4(src.rgb, src.a - bloom.a); } else if(uBlendMode == 5) { - result = blendHardLight(src, bloom); - } else if(uBlendMode == 6) { - result = vec4(1.0) - src; - } else if(uBlendMode == 7) { - result = src; + result = blendOverlay(src, bloom); } else if(uBlendMode == 8) { result = blendLighten(src, bloom); } else if(uBlendMode == 9) { result = blendMultiply(src, bloom); - } else if(uBlendMode == 10) { - result = bloom; } else if(uBlendMode == 11) { result = blendOverlay(src, bloom); } else if(uBlendMode == 12) { result = blendScreen(src, bloom); - } else if(uBlendMode == 13) { - result = src + bloom; - } else if(uBlendMode == 14) { - result = src + bloom - 1.0; } else if(uBlendMode == 15) { result = blendColorDodge(src, bloom); - } else if(uBlendMode == 16) { - result = blendColorBurn(src, bloom); } else if(uBlendMode == 17) { result = blendSoftLight(src, bloom); - } else if(uBlendMode == 18) { - result = blendExclusion(src, bloom); - } else if(uBlendMode == 19) { - result = blendHue(src, bloom); - } else if(uBlendMode == 20) { - result = blendSaturation(src, bloom); - } else if(uBlendMode == 21) { - result = blendColor(src, bloom); - } else if(uBlendMode == 22) { - result = blendLuminosity(src, bloom); } else { result = src + bloom; } @@ -937,13 +783,12 @@ private class CombineShader extends BitmapFilterShader attribute vec2 openfl_TextureCoord; 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) / uQuality); + textureCoords = vec4(openfl_TextureCoord, openfl_TextureCoord / uQuality); } ") public function new() @@ -951,8 +796,8 @@ private class CombineShader extends BitmapFilterShader super(); #if !macro - offset.value = [0, 0]; uStrength.value = [1.0]; + uQuality.value = [8]; uThreshold.value = [0.6]; uBlendMode.value = [0]; #end From 181d18beade0161f018c8705c9d5619293005a6d Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Tue, 3 Mar 2026 22:50:06 +0800 Subject: [PATCH 18/20] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20BloomEffect.hx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/funkin/backend/shaders/BloomEffect.hx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/source/funkin/backend/shaders/BloomEffect.hx b/source/funkin/backend/shaders/BloomEffect.hx index 751fd6b39b..e6dd7e6449 100644 --- a/source/funkin/backend/shaders/BloomEffect.hx +++ b/source/funkin/backend/shaders/BloomEffect.hx @@ -161,7 +161,7 @@ class BloomEffect extends BitmapFilter @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). + 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 @@ -621,15 +621,15 @@ private class ExtractLowShader extends BitmapFilterShader private class ExtractShader extends BitmapFilterShader { @:glFragmentSource(" - uniform sampler2D openfl_Texture; + uniform sampler2D openfl_Texture; uniform vec2 openfl_TextureSize; - uniform float uThreshold; - uniform float uQuality; - uniform vec3 uWeights; - varying vec2 vTexCoord; + uniform float uThreshold; + uniform float uQuality; + uniform vec3 uWeights; + varying vec2 vTexCoord; varying vec4 border; - void main(void) { + void main(void) { if ((all(greaterThanEqual(vTexCoord, border.xy)) && all(lessThanEqual(vTexCoord, border.zw))) == false) return; float quality = floor(uQuality) / 2.0; @@ -652,7 +652,7 @@ private class ExtractShader extends BitmapFilterShader } gl_FragColor = accumulated / float(sampleCount); - } + } ") @:glVertexSource(" attribute vec4 openfl_Position; From cb0b1b646c166444b9ea53ed643d7bfebe06ab72 Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Tue, 3 Mar 2026 23:02:55 +0800 Subject: [PATCH 19/20] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20BloomEffect.hx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/funkin/backend/shaders/BloomEffect.hx | 452 +++++++++---------- 1 file changed, 219 insertions(+), 233 deletions(-) diff --git a/source/funkin/backend/shaders/BloomEffect.hx b/source/funkin/backend/shaders/BloomEffect.hx index e6dd7e6449..9b34905451 100644 --- a/source/funkin/backend/shaders/BloomEffect.hx +++ b/source/funkin/backend/shaders/BloomEffect.hx @@ -23,8 +23,6 @@ import openfl.geom.Rectangle; 2. Blurring - The extracted bright areas are blurred horizontally and vertically 3. Combination - The blurred result is blended back with the original image **/ -@:access(openfl.geom.Point) -@:access(openfl.geom.Rectangle) class BloomEffect extends BitmapFilter { /** @@ -212,7 +210,6 @@ class BloomEffect extends BitmapFilter @:noCompletion private override function __initShader(renderer:DisplayObjectRenderer, pass:Int, sourceBitmapData:BitmapData):Shader { - #if !macro final numBlurPasses = __horizontalPasses + __verticalPasses; switch pass @@ -255,9 +252,6 @@ class BloomEffect extends BitmapFilter __combineShader.uBlendMode.value[0] = cast __blendMode; return __combineShader; } - #else - return null; - #end } // Get & Set Methods @@ -497,71 +491,69 @@ class BloomEffect extends BitmapFilter private class BlurShader extends BitmapFilterShader { @:glFragmentSource(" - uniform sampler2D openfl_Texture; - - varying mat2 vBlurCoord0; - varying mat2 vBlurCoord1; - varying vec2 vBlurCoord2; - varying mat2 vBlurCoord3; - varying mat2 vBlurCoord4; - - 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; - 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; - } +uniform sampler2D openfl_Texture; + +varying mat2 vBlurCoord0; +varying mat2 vBlurCoord1; +varying vec2 vBlurCoord2; +varying mat2 vBlurCoord3; +varying mat2 vBlurCoord4; + +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; + 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; +} ") @:glVertexSource(" - attribute vec4 openfl_Position; - attribute vec2 openfl_TextureCoord; - - uniform mat4 openfl_Matrix; - - uniform vec2 uRadius; - uniform vec2 uTextureSize; - uniform float uQuality; - - varying mat2 vBlurCoord0; - varying mat2 vBlurCoord1; - varying vec2 vBlurCoord2; - varying mat2 vBlurCoord3; - varying mat2 vBlurCoord4; - - void main(void) { - vec4 pos = openfl_Position; - pos.xy /= uQuality; - gl_Position = openfl_Matrix * pos; - - vec2 r = uRadius / uTextureSize; - 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; - } +attribute vec4 openfl_Position; +attribute vec2 openfl_TextureCoord; + +uniform mat4 openfl_Matrix; + +uniform vec2 uRadius; +uniform vec2 uTextureSize; +uniform float uQuality; + +varying mat2 vBlurCoord0; +varying mat2 vBlurCoord1; +varying vec2 vBlurCoord2; +varying mat2 vBlurCoord3; +varying mat2 vBlurCoord4; + +void main(void) { + vec4 pos = openfl_Position; + pos.xy /= uQuality; + gl_Position = openfl_Matrix * pos; + + vec2 r = uRadius / uTextureSize; + 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() { super(); - #if !macro uRadius.value = [0, 0]; uQuality.value = [8]; - #end } @:noCompletion private override function __update():Void @@ -577,229 +569,223 @@ private class BlurShader extends BitmapFilterShader private class ExtractLowShader extends BitmapFilterShader { @:glFragmentSource(" - uniform sampler2D openfl_Texture; - uniform float uThreshold; - uniform vec3 uWeights; - 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 = min(dot(texel.rgb, uWeights), 1.0); - float mask = smoothstep(uThreshold, uThreshold + 0.1, brightness); - gl_FragColor = texel * mask; - } +uniform sampler2D openfl_Texture; +uniform float uThreshold; +uniform vec3 uWeights; +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 = min(dot(texel.rgb, uWeights), 1.0); + 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; - 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; - } +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]; uQuality.value = [8]; uWeights.value = [0.2126, 0.7152, 0.0722]; - #end } } private class ExtractShader extends BitmapFilterShader { @:glFragmentSource(" - uniform sampler2D openfl_Texture; - uniform vec2 openfl_TextureSize; - uniform float uThreshold; - uniform float uQuality; - uniform vec3 uWeights; - varying vec2 vTexCoord; - varying vec4 border; - - void main(void) { - 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; - - 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; - - vec4 texel = texture2D(openfl_Texture, sampleCoord); - float brightness = min(dot(texel.rgb, uWeights), 1.0); - float mask = smoothstep(uThreshold, uThreshold + 0.1, brightness); - accumulated += texel * mask; - sampleCount++; - } - } - - gl_FragColor = accumulated / float(sampleCount); +uniform sampler2D openfl_Texture; +uniform vec2 openfl_TextureSize; +uniform float uThreshold; +uniform float uQuality; +uniform vec3 uWeights; +varying vec2 vTexCoord; +varying vec4 border; + +void main(void) { + 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; + + 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; + + vec4 texel = texture2D(openfl_Texture, sampleCoord); + float brightness = min(dot(texel.rgb, uWeights), 1.0); + 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; - 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; - } +attribute vec4 openfl_Position; +attribute vec2 openfl_TextureCoord; +uniform mat4 openfl_Matrix; +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; +} ") public function new() { super(); - #if !macro uThreshold.value = [0.6]; uQuality.value = [8]; uWeights.value = [0.2126, 0.7152, 0.0722]; - #end } } private class CombineShader extends BitmapFilterShader { @:glFragmentSource(" - uniform sampler2D openfl_Texture; - uniform sampler2D sourceBitmap; - uniform float uStrength; - uniform float uThreshold; - uniform int uBlendMode; - varying vec4 textureCoords; - - vec4 blendScreen(vec4 src, vec4 bloom) { - return vec4(1.0) - (vec4(1.0) - src) * (vec4(1.0) - bloom); - } +uniform sampler2D openfl_Texture; +uniform sampler2D sourceBitmap; +uniform float uStrength; +uniform float uThreshold; +uniform int uBlendMode; +varying vec4 textureCoords; + +vec4 blendScreen(vec4 src, vec4 bloom) { + return vec4(1.0) - (vec4(1.0) - src) * (vec4(1.0) - bloom); +} - vec4 blendMultiply(vec4 src, vec4 bloom) { - return src * bloom; - } +vec4 blendMultiply(vec4 src, vec4 bloom) { + return src * bloom; +} - vec4 blendLighten(vec4 src, vec4 bloom) { - return max(src, bloom); - } +vec4 blendLighten(vec4 src, vec4 bloom) { + return max(src, bloom); +} - vec4 blendOverlay(vec4 src, vec4 bloom) { - vec4 result = vec4(0.0); - for (int i = 0; i < 4; i++) { - if (src[i] < 0.5) { - result[i] = 2.0 * src[i] * bloom[i]; - } else { - result[i] = 1.0 - 2.0 * (1.0 - src[i]) * (1.0 - bloom[i]); - } - } - return result; +vec4 blendOverlay(vec4 src, vec4 bloom) { + vec4 result = vec4(0.0); + for (int i = 0; i < 4; i++) { + if (src[i] < 0.5) { + result[i] = 2.0 * src[i] * bloom[i]; + } else { + result[i] = 1.0 - 2.0 * (1.0 - src[i]) * (1.0 - bloom[i]); } + } + return result; +} - vec4 blendColorDodge(vec4 src, vec4 bloom) { - vec4 result = vec4(0.0); - for (int i = 0; i < 4; i++) { - if (bloom[i] < 1.0) { - result[i] = min(1.0, src[i] / (1.0 - bloom[i])); - } else { - result[i] = 1.0; - } - } - return result; - } - - vec4 blendSoftLight(vec4 src, vec4 bloom) { - vec4 result = vec4(0.0); - for (int i = 0; i < 4; i++) { - if (bloom[i] < 0.5) { - result[i] = src[i] - (1.0 - 2.0 * bloom[i]) * src[i] * (1.0 - src[i]); - } else { - float d = (src[i] <= 0.25) ? ((16.0 * src[i] - 12.0) * src[i] + 4.0) * src[i] : sqrt(src[i]); - result[i] = src[i] + (2.0 * bloom[i] - 1.0) * (d - src[i]); - } - } - return result; +vec4 blendColorDodge(vec4 src, vec4 bloom) { + vec4 result = vec4(0.0); + for (int i = 0; i < 4; i++) { + if (bloom[i] < 1.0) { + result[i] = min(1.0, src[i] / (1.0 - bloom[i])); + } else { + result[i] = 1.0; } + } + return result; +} - vec4 blendAlpha(vec4 src, vec4 bloom) { - return src + bloom * (1.0 - src.a); +vec4 blendSoftLight(vec4 src, vec4 bloom) { + vec4 result = vec4(0.0); + for (int i = 0; i < 4; i++) { + if (bloom[i] < 0.5) { + result[i] = src[i] - (1.0 - 2.0 * bloom[i]) * src[i] * (1.0 - src[i]); + } else { + float d = (src[i] <= 0.25) ? ((16.0 * src[i] - 12.0) * src[i] + 4.0) * src[i] : sqrt(src[i]); + result[i] = src[i] + (2.0 * bloom[i] - 1.0) * (d - src[i]); } + } + return result; +} - void main(void) { - vec4 src = texture2D(sourceBitmap, textureCoords.xy); - vec4 bloom = texture2D(openfl_Texture, textureCoords.zw) * uStrength; - - vec4 result; - if(uBlendMode == 0) { - result = src + bloom; - } else if(uBlendMode == 1) { - result = blendAlpha(src, bloom); - } else if(uBlendMode == 5) { - result = blendOverlay(src, bloom); - } else if(uBlendMode == 8) { - result = blendLighten(src, bloom); - } else if(uBlendMode == 9) { - result = blendMultiply(src, bloom); - } else if(uBlendMode == 11) { - result = blendOverlay(src, bloom); - } else if(uBlendMode == 12) { - result = blendScreen(src, bloom); - } else if(uBlendMode == 15) { - result = blendColorDodge(src, bloom); - } else if(uBlendMode == 17) { - result = blendSoftLight(src, bloom); - } else { - result = src + bloom; - } +vec4 blendAlpha(vec4 src, vec4 bloom) { + return src + bloom * (1.0 - src.a); +} - gl_FragColor = result; - } +void main(void) { + vec4 src = texture2D(sourceBitmap, textureCoords.xy); + vec4 bloom = texture2D(openfl_Texture, textureCoords.zw) * uStrength; + + vec4 result; + if(uBlendMode == 0) + result = src + bloom; + else if(uBlendMode == 1) + result = blendAlpha(src, bloom); + else if(uBlendMode == 5) + result = blendOverlay(src, bloom); + else if(uBlendMode == 8) + result = blendLighten(src, bloom); + else if(uBlendMode == 9) + result = blendMultiply(src, bloom); + else if(uBlendMode == 11) + result = blendOverlay(src, bloom); + else if(uBlendMode == 12) + result = blendScreen(src, bloom); + else if(uBlendMode == 15) + result = blendColorDodge(src, bloom); + else if(uBlendMode == 17) + result = blendSoftLight(src, bloom); + else + result = src + bloom; + + gl_FragColor = result; +} ") - @:glVertexSource("attribute vec4 openfl_Position; - attribute vec2 openfl_TextureCoord; - uniform mat4 openfl_Matrix; - uniform vec2 openfl_TextureSize; - uniform float uQuality; - varying vec4 textureCoords; - - void main(void) { - gl_Position = openfl_Matrix * openfl_Position; - textureCoords = vec4(openfl_TextureCoord, openfl_TextureCoord / uQuality); - } + @:glVertexSource(" +attribute vec4 openfl_Position; +attribute vec2 openfl_TextureCoord; +uniform mat4 openfl_Matrix; +uniform vec2 openfl_TextureSize; +uniform float uQuality; +varying vec4 textureCoords; + +void main(void) { + gl_Position = openfl_Matrix * openfl_Position; + textureCoords = vec4(openfl_TextureCoord, openfl_TextureCoord / uQuality); +} ") public function new() { super(); - #if !macro uStrength.value = [1.0]; uQuality.value = [8]; uThreshold.value = [0.6]; uBlendMode.value = [0]; - #end } } \ No newline at end of file From fbf11bcdddce17bcd1acd0e659cacbedefbd716f Mon Sep 17 00:00:00 2001 From: HEIHUAa <112499486+HEIHUAa@users.noreply.github.com> Date: Tue, 3 Mar 2026 23:25:12 +0800 Subject: [PATCH 20/20] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20BloomEffect.hx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/funkin/backend/shaders/BloomEffect.hx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/source/funkin/backend/shaders/BloomEffect.hx b/source/funkin/backend/shaders/BloomEffect.hx index 9b34905451..9c68360ff5 100644 --- a/source/funkin/backend/shaders/BloomEffect.hx +++ b/source/funkin/backend/shaders/BloomEffect.hx @@ -81,8 +81,9 @@ class BloomEffect extends BitmapFilter 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. + Low-quality pixel sampling mode. Disabling it significantly reduces screen flickering, + with minimal performance impact on desktop platforms but a higher + performance cost on non-desktop platforms. **/ public var useLowQualityExtract(get, set):Bool; @@ -204,7 +205,7 @@ class BloomEffect extends BitmapFilter @:noCompletion private override function __applyFilter(bitmapData:BitmapData, sourceBitmapData:BitmapData, sourceRect:Rectangle, destPoint:Point):BitmapData { - trace('BloomEffect does not support bitmapData rendering functionality.'); + trace("Due to technical limitations, I'm unable to implement the bitmapData rendering method. If you know how to implement it, you're welcome to contribute this feature."); return sourceBitmapData; }