1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-02-20 12:33:14 +00:00

Optimize layer blending modes and make them work on all GPUs (hopefully) - Fixes #938

This commit is contained in:
Emmanouil Papadeas 2023-11-25 00:10:19 +02:00
parent 88a2ef593e
commit bc8a9de4db
14 changed files with 170 additions and 117 deletions

View file

@ -9,64 +9,39 @@ var omniscale_shader := preload("res://src/Shaders/Effects/Rotation/OmniScale.gd
## Blends canvas layers into passed image starting from the origin position
func blend_all_layers(
image: Image, frame: Frame, origin := Vector2i.ZERO, project := Global.current_project
) -> void:
var current_cels := frame.cels
var textures: Array[Image] = []
var opacities := PackedFloat32Array()
var blend_modes := PackedInt32Array()
for i in Global.current_project.layers.size():
if current_cels[i] is GroupCel:
continue
var layer := Global.current_project.layers[i]
if not layer.is_visible_in_hierarchy():
continue
var cel_image := layer.display_effects(current_cels[i])
textures.append(cel_image)
opacities.append(current_cels[i].opacity)
blend_modes.append(layer.blend_mode)
var texture_array := Texture2DArray.new()
texture_array.create_from_images(textures)
var params := {
"layers": texture_array,
"opacities": opacities,
"blend_modes": blend_modes,
}
var blended := Image.create(project.size.x, project.size.y, false, image.get_format())
var gen := ShaderImageEffect.new()
gen.generate_image(blended, blend_layers_shader, params, project.size)
image.blend_rect(blended, Rect2i(Vector2i.ZERO, project.size), origin)
## Blends selected cels of the given frame into passed image starting from the origin position
func blend_selected_cels(
image: Image, frame: Frame, origin := Vector2i.ZERO, project := Global.current_project
func blend_layers(
image: Image,
frame: Frame,
origin := Vector2i.ZERO,
project := Global.current_project,
only_selected := false
) -> void:
var textures: Array[Image] = []
var opacities := PackedFloat32Array()
var blend_modes := PackedInt32Array()
for cel_ind in frame.cels.size():
var test_array := [project.current_frame, cel_ind]
if not test_array in project.selected_cels:
continue
if frame.cels[cel_ind] is GroupCel:
continue
var layer := project.layers[cel_ind]
if not layer.is_visible_in_hierarchy():
continue
var cel := frame.cels[cel_ind]
# Nx3 texture, where N is the number of layers and the first row are the blend modes,
# the second are the opacities and the third are the origins
var metadata_image := Image.create(project.layers.size(), 3, false, Image.FORMAT_R8)
for i in project.layers.size():
var layer := project.layers[i]
var include := true if layer.is_visible_in_hierarchy() else false
if only_selected and include:
var test_array := [project.frames.find(frame), i]
if not test_array in project.selected_cels:
include = false
var cel := frame.cels[i]
var cel_image := layer.display_effects(cel)
textures.append(cel_image)
opacities.append(cel.opacity)
blend_modes.append(layer.blend_mode)
# Store the blend mode
metadata_image.set_pixel(i, 0, Color(layer.blend_mode / 255.0, 0.0, 0.0, 0.0))
# Store the opacity
if include:
metadata_image.set_pixel(i, 1, Color(cel.opacity, 0.0, 0.0, 0.0))
else:
metadata_image.set_pixel(i, 1, Color())
var texture_array := Texture2DArray.new()
texture_array.create_from_images(textures)
var params := {
"layers": texture_array,
"opacities": opacities,
"blend_modes": blend_modes,
"metadata": ImageTexture.create_from_image(metadata_image),
}
var blended := Image.create(project.size.x, project.size.y, false, image.get_format())
var gen := ShaderImageEffect.new()

View file

@ -484,9 +484,9 @@ func _blend_layers(
image: Image, frame: Frame, origin := Vector2i.ZERO, project := Global.current_project
) -> void:
if export_layers == 0:
DrawingAlgos.blend_all_layers(image, frame, origin, project)
DrawingAlgos.blend_layers(image, frame, origin, project)
elif export_layers == 1:
DrawingAlgos.blend_selected_cels(image, frame, origin, project)
DrawingAlgos.blend_layers(image, frame, origin, project, true)
else:
var layer := project.layers[export_layers - 2]
var layer_image := Image.new()

View file

@ -196,10 +196,10 @@ func set_and_update_preview_image(frame_idx: int) -> void:
var frame := Global.current_project.frames[frame_idx]
selected_cels.resize(Global.current_project.size.x, Global.current_project.size.y)
selected_cels.fill(Color(0, 0, 0, 0))
DrawingAlgos.blend_selected_cels(selected_cels, frame)
DrawingAlgos.blend_layers(selected_cels, frame, Vector2i.ZERO, Global.current_project, true)
current_frame.resize(Global.current_project.size.x, Global.current_project.size.y)
current_frame.fill(Color(0, 0, 0, 0))
DrawingAlgos.blend_all_layers(current_frame, frame)
DrawingAlgos.blend_layers(current_frame, frame)
update_preview()

View file

@ -538,6 +538,7 @@ func change_cel(new_frame: int, new_layer := -1) -> void:
if get_current_cel() is Cel3D:
await RenderingServer.frame_post_draw
await RenderingServer.frame_post_draw
Global.canvas.update_all_layers = true
Global.canvas.queue_redraw()

View file

@ -5,9 +5,11 @@ const float HCV_EPSILON = 1e-10;
const float HSL_EPSILON = 1e-10;
uniform sampler2DArray layers : filter_nearest;
uniform float[1024] opacities;
uniform int[1024] blend_modes;
uniform vec2[1024] origins;
// Nx3 texture, where N is the number of layers and the first row are the blend modes,
// the second are the opacities and the third are the origins
uniform sampler2D metadata : filter_nearest;
uniform bool origin_x_positive = true;
uniform bool origin_y_positive = true;
// Conversion functions from
// https://gist.github.com/unitycoder/aaf94ddfe040ec2da93b58d3c65ab9d9
@ -54,7 +56,7 @@ vec3 rgb_to_hsl(vec3 rgb)
vec4 blend(int blend_type, vec4 current_color, vec4 prev_color, float opacity) {
vec4 result;
if (current_color.a <= 0.01) {
if (current_color.a <= 0.001 || opacity <= 0.001) {
return prev_color;
}
current_color.rgb = current_color.rgb * opacity; // Premultiply with the layer texture's alpha to prevent semi transparent pixels from being too bright (ALL LAYER TYPES!)
@ -142,15 +144,36 @@ float border_trim(vec4 color, vec2 uv) {
void fragment() {
vec4 col = texture(layers, vec3(UV - origins[0], 0.0));
col.a = border_trim(col, UV - origins[0]);
col.a *= opacities[0];
ivec2 metadata_size = textureSize(metadata, 0) - 1;
vec2 first_origin = texture(metadata, vec2(0.0, 2.0 / float(metadata_size.y))).rg;
if (!origin_x_positive) {
first_origin.x = -first_origin.x;
}
if (!origin_y_positive) {
first_origin.y = -first_origin.y;
}
float first_opacity = texture(metadata, vec2(0.0, 1.0 / float(metadata_size.y))).r;
vec4 col = texture(layers, vec3(UV - first_origin, 0.0));
col.a = border_trim(col, UV - first_origin);
col.a *= first_opacity;
for(int i = 1; i < textureSize(layers, 0).z; i++) // Loops through every layer
{
vec2 uv = UV - origins[i];
float blend_mode_float = texture(metadata, vec2(float(i) / float(metadata_size.x), 0.0)).r;
// Blend modes are being stored as integers divided by 255, so convert them back to
// their integer form
int current_blend_mode = int(floor(blend_mode_float * 255.0));
vec2 current_origin = texture(metadata, vec2(float(i) / float(metadata_size.x), 2.0 / float(metadata_size.y))).rg;
if (!origin_x_positive) {
current_origin.x = -current_origin.x;
}
if (!origin_y_positive) {
current_origin.y = -current_origin.y;
}
float current_opacity = texture(metadata, vec2(float(i) / float(metadata_size.x), 1.0 / float(metadata_size.y))).r;
vec2 uv = UV - current_origin;
vec4 texture_color = texture(layers, vec3(uv, float(i)));
texture_color.a = border_trim(texture_color, uv);
col = blend(blend_modes[i], texture_color, col, opacities[i]);
col = blend(current_blend_mode, texture_color, col, current_opacity);
}
COLOR = col;
}

View file

@ -6,7 +6,11 @@ const CURSOR_SPEED_RATE := 6.0
var current_pixel := Vector2.ZERO
var sprite_changed_this_frame := false ## For optimization purposes
var update_all_layers := false
var move_preview_location := Vector2i.ZERO
var layer_texture_array := Texture2DArray.new()
var layer_metadata_image := Image.new()
var layer_metadata_texture := ImageTexture.new()
@onready var currently_visible_frame := $CurrentlyVisibleFrame as SubViewport
@onready var current_frame_drawer := $CurrentlyVisibleFrame/CurrentFrameDrawer as Node2D
@ -108,6 +112,19 @@ func update_texture(layer_i: int, frame_i := -1, project := Global.current_proje
if frame_i < project.frames.size() and layer_i < project.layers.size():
var current_cel := project.frames[frame_i].cels[layer_i]
current_cel.update_texture()
# Needed so that changes happening to the non-selected layer(s) are also visible
# e.g. when undoing/redoing, when applying image effects to the entire frame, etc
var layer := project.layers[layer_i]
var cel_image: Image
if Global.display_layer_effects:
cel_image = layer.display_effects(current_cel)
else:
cel_image = current_cel.get_image()
if (
cel_image.get_size()
== Vector2i(layer_texture_array.get_width(), layer_texture_array.get_height())
):
layer_texture_array.update_layer(cel_image, layer_i)
func update_selected_cels_textures(project := Global.current_project) -> void:
@ -120,36 +137,72 @@ func update_selected_cels_textures(project := Global.current_project) -> void:
func draw_layers() -> void:
var current_frame := Global.current_project.frames[Global.current_project.current_frame]
var current_cels := current_frame.cels
var textures: Array[Image] = []
var opacities := PackedFloat32Array()
var blend_modes := PackedInt32Array()
var origins := PackedVector2Array()
# Draw current frame layers
for i in Global.current_project.layers.size():
if current_cels[i] is GroupCel:
continue
var layer := Global.current_project.layers[i]
if layer.is_visible_in_hierarchy():
var project := Global.current_project
var current_cels := project.frames[project.current_frame].cels
var recreate_texture_array := (
layer_texture_array.get_layers() != project.layers.size()
or layer_texture_array.get_width() != project.size.x
or layer_texture_array.get_height() != project.size.y
)
if recreate_texture_array:
var textures: Array[Image] = []
# Nx3 texture, where N is the number of layers and the first row are the blend modes,
# the second are the opacities and the third are the origins
layer_metadata_image = Image.create(project.layers.size(), 3, false, Image.FORMAT_RG8)
# Draw current frame layers
for i in project.layers.size():
var layer := project.layers[i]
var cel_image: Image
if Global.display_layer_effects:
cel_image = layer.display_effects(current_cels[i])
else:
cel_image = current_cels[i].get_image()
textures.append(cel_image)
opacities.append(current_cels[i].opacity)
if [Global.current_project.current_frame, i] in Global.current_project.selected_cels:
origins.append(Vector2(move_preview_location) / Vector2(cel_image.get_size()))
# Store the blend mode
layer_metadata_image.set_pixel(i, 0, Color(layer.blend_mode / 255.0, 0.0, 0.0, 0.0))
# Store the opacity
if layer.is_visible_in_hierarchy():
layer_metadata_image.set_pixel(i, 1, Color(current_cels[i].opacity, 0.0, 0.0, 0.0))
else:
origins.append(Vector2.ZERO)
blend_modes.append(layer.blend_mode)
var texture_array := Texture2DArray.new()
texture_array.create_from_images(textures)
material.set_shader_parameter("layers", texture_array)
material.set_shader_parameter("opacities", opacities)
material.set_shader_parameter("blend_modes", blend_modes)
material.set_shader_parameter("origins", origins)
layer_metadata_image.set_pixel(i, 1, Color())
# Store the origin
if [project.current_frame, i] in project.selected_cels:
var origin := Vector2(move_preview_location).abs() / Vector2(cel_image.get_size())
layer_metadata_image.set_pixel(i, 2, Color(origin.x, origin.y, 0.0, 0.0))
else:
layer_metadata_image.set_pixel(i, 2, Color())
layer_texture_array.create_from_images(textures)
layer_metadata_texture.set_image(layer_metadata_image)
else: # Update the TextureArray
if layer_texture_array.get_layers() > 0:
for i in project.layers.size():
var layer := project.layers[i]
var test_array := [project.current_frame, i]
if not update_all_layers:
if not test_array in project.selected_cels:
continue
var cel := current_cels[i]
var cel_image: Image
if Global.display_layer_effects:
cel_image = layer.display_effects(cel)
else:
cel_image = cel.get_image()
layer_texture_array.update_layer(cel_image, i)
layer_metadata_image.set_pixel(i, 0, Color(layer.blend_mode / 255.0, 0.0, 0.0, 0.0))
if layer.is_visible_in_hierarchy():
layer_metadata_image.set_pixel(i, 1, Color(cel.opacity, 0.0, 0.0, 0.0))
else:
layer_metadata_image.set_pixel(i, 1, Color())
var origin := Vector2(move_preview_location).abs() / Vector2(cel_image.get_size())
layer_metadata_image.set_pixel(i, 2, Color(origin.x, origin.y, 0.0, 0.0))
layer_metadata_texture.update(layer_metadata_image)
material.set_shader_parameter("layers", layer_texture_array)
material.set_shader_parameter("metadata", layer_metadata_texture)
material.set_shader_parameter("origin_x_positive", move_preview_location.x > 0)
material.set_shader_parameter("origin_y_positive", move_preview_location.y > 0)
update_all_layers = false
func refresh_onion() -> void:

View file

@ -19,9 +19,8 @@
[sub_resource type="ShaderMaterial" id="ShaderMaterial_6b0ox"]
shader = ExtResource("1_253dh")
shader_parameter/opacities = null
shader_parameter/blend_modes = null
shader_parameter/origins = null
shader_parameter/origin_x_positive = true
shader_parameter/origin_y_positive = true
[sub_resource type="CanvasItemMaterial" id="1"]
blend_mode = 4

View file

@ -72,30 +72,33 @@ func _draw() -> void:
func _draw_layers() -> void:
var current_frame := Global.current_project.frames[frame_index]
var project := Global.current_project
var current_frame := project.frames[frame_index]
var current_cels := current_frame.cels
var textures: Array[Image] = []
var opacities := PackedFloat32Array()
var blend_modes := PackedInt32Array()
# Nx3 texture, where N is the number of layers and the first row are the blend modes,
# the second are the opacities and the third are the origins
var metadata_image := Image.create(project.layers.size(), 3, false, Image.FORMAT_R8)
# Draw current frame layers
for i in Global.current_project.layers.size():
for i in project.layers.size():
if current_cels[i] is GroupCel:
continue
var layer := Global.current_project.layers[i]
var layer := project.layers[i]
var cel_image: Image
if Global.display_layer_effects:
cel_image = layer.display_effects(current_cels[i])
else:
cel_image = current_cels[i].get_image()
textures.append(cel_image)
metadata_image.set_pixel(i, 0, Color(layer.blend_mode / 255.0, 0.0, 0.0, 0.0))
if layer.is_visible_in_hierarchy():
var cel_image: Image
if Global.display_layer_effects:
cel_image = layer.display_effects(current_cels[i])
else:
cel_image = current_cels[i].get_image()
textures.append(cel_image)
opacities.append(current_cels[i].opacity)
blend_modes.append(layer.blend_mode)
metadata_image.set_pixel(i, 1, Color(current_cels[i].opacity, 0.0, 0.0, 0.0))
else:
metadata_image.set_pixel(i, 1, Color(0.0, 0.0, 0.0, 0.0))
var texture_array := Texture2DArray.new()
texture_array.create_from_images(textures)
material.set_shader_parameter("layers", texture_array)
material.set_shader_parameter("opacities", opacities)
material.set_shader_parameter("blend_modes", blend_modes)
material.set_shader_parameter("metadata", ImageTexture.create_from_image(metadata_image))
func _on_AnimationTimer_timeout() -> void:

View file

@ -878,7 +878,9 @@ func clear_selection(use_undo := false) -> void:
func _get_preview_image() -> void:
var project := Global.current_project
var blended_image := Image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)
DrawingAlgos.blend_selected_cels(blended_image, project.frames[project.current_frame])
DrawingAlgos.blend_layers(
blended_image, project.frames[project.current_frame], Vector2i.ZERO, project, true
)
if original_preview_image.is_empty():
original_preview_image = Image.create(
big_bounding_rectangle.size.x, big_bounding_rectangle.size.y, false, Image.FORMAT_RGBA8

View file

@ -10,7 +10,7 @@ var params := {}
func _about_to_popup() -> void:
Global.canvas.selection.transform_content_confirm()
var frame := Global.current_project.frames[Global.current_project.current_frame]
DrawingAlgos.blend_selected_cels(selected_cels, frame)
DrawingAlgos.blend_layers(selected_cels, frame, Vector2i.ZERO, Global.current_project, true)
preview_image.copy_from(selected_cels)
preview.texture = ImageTexture.create_from_image(preview_image)

View file

@ -125,7 +125,7 @@ func change_mask():
var tiles := Global.current_project.tiles
var tiles_size := tiles.tile_size
var image := Image.create(tiles_size.x, tiles_size.y, false, Image.FORMAT_RGBA8)
DrawingAlgos.blend_all_layers(image, current_frame)
DrawingAlgos.blend_layers(image, current_frame)
if (
image.get_used_rect().size == Vector2i.ZERO
or not $VBoxContainer/HBoxContainer/Masking.button_pressed

View file

@ -74,7 +74,7 @@ func capture_frame() -> void:
else:
var frame := project.frames[project.current_frame]
image = Image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)
DrawingAlgos.blend_all_layers(image, frame, Vector2i.ZERO, project)
DrawingAlgos.blend_layers(image, frame, Vector2i.ZERO, project)
if mode == Mode.CANVAS:
if resize != 100:

View file

@ -917,20 +917,16 @@ func _on_MergeDownLayer_pressed() -> void:
var top_image := top_layer.display_effects(top_cel)
var bottom_cel := frame.cels[bottom_layer.index]
var textures: Array[Image] = []
var opacities := PackedFloat32Array()
var blend_modes := PackedInt32Array()
var metadata_image := Image.create(2, 3, false, Image.FORMAT_R8)
textures.append(bottom_cel.get_image())
opacities.append(bottom_cel.opacity)
blend_modes.append(bottom_layer.blend_mode)
metadata_image.set_pixel(0, 1, Color(1.0, 0.0, 0.0, 0.0))
textures.append(top_image)
opacities.append(frame.cels[top_layer.index].opacity)
blend_modes.append(top_layer.blend_mode)
metadata_image.set_pixel(1, 0, Color(top_layer.blend_mode / 255.0, 0.0, 0.0, 0.0))
metadata_image.set_pixel(1, 1, Color(frame.cels[top_layer.index].opacity, 0.0, 0.0, 0.0))
var texture_array := Texture2DArray.new()
texture_array.create_from_images(textures)
var params := {
"layers": texture_array,
"opacities": opacities,
"blend_modes": blend_modes,
"layers": texture_array, "metadata": ImageTexture.create_from_image(metadata_image)
}
var bottom_image := Image.create(
top_image.get_width(), top_image.get_height(), false, top_image.get_format()

View file

@ -155,6 +155,7 @@ func _on_ExpandButton_pressed() -> void:
func _on_VisibilityButton_pressed() -> void:
Global.canvas.selection.transform_content_confirm()
Global.current_project.layers[layer].visible = !Global.current_project.layers[layer].visible
Global.canvas.update_all_layers = true
Global.canvas.queue_redraw()
if Global.select_layer_on_button_click:
_select_current_layer()