From 971299f062c11e4a40154fe7b8947d19c0822bac Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Wed, 24 Dec 2025 14:36:54 -0600 Subject: [PATCH] multiple effects --- film2d.cm | 237 ++++++++++++++++-------------------------------------- 1 file changed, 69 insertions(+), 168 deletions(-) diff --git a/film2d.cm b/film2d.cm index b9502d8b..3beab615 100644 --- a/film2d.cm +++ b/film2d.cm @@ -133,79 +133,48 @@ function process_scene_tree(node, camera, ctx, parent_tint, parent_opacity, pare return collect_drawables(node, camera, parent_tint, parent_opacity, parent_scissor, parent_pos, ctx) } -// Process a group with effects - creates an "island" if needed +// Process a group with effects - creates an "island" and applies effects sequentially function process_effect_group(node, camera, ctx, parent_tint, parent_opacity, parent_scissor, parent_pos) { var effects = node.effects - var drawables = [] + var backend = ctx.backend + var target_size = ctx.target_size - // For each effect, determine if we need an island + // 1. Render all children to an initial content target + var current_target = backend.get_or_create_target(target_size.width, target_size.height, 'effect_start_' + ctx.target_counter++) + var child_drawables = collect_children_drawables(node, camera, parent_tint, parent_opacity, parent_scissor, parent_pos, ctx) + + render_drawables_to_target(child_drawables, current_target, camera, ctx) + + // 2. Apply effects sequentially, each one consuming the previous target and producing a new one for (var effect of effects) { var effect_type = effect.type if (effect_type == 'bloom') { - // Bloom requires an island - render children to intermediate, apply bloom, return as single drawable - var island_result = render_bloom_island(node, camera, ctx, effect, parent_tint, parent_opacity, parent_scissor, parent_pos) - if (island_result) { - drawables.push(island_result) - } - return drawables - } - - if (effect_type == 'mask') { - // Mask - check if soft (needs texture) or hard (can use stencil) - var soft = effect.soft || false - if (soft) { - // Soft mask needs island - var island_result = render_mask_island(node, camera, ctx, effect, parent_tint, parent_opacity, parent_scissor, parent_pos) - if (island_result) { - drawables.push(island_result) - } - return drawables - } else { - // Hard mask - can potentially use stencil inline - // For now, still use island approach for simplicity - var island_result = render_mask_island(node, camera, ctx, effect, parent_tint, parent_opacity, parent_scissor, parent_pos) - if (island_result) { - drawables.push(island_result) - } - return drawables - } - } - - if (effect_type == 'blur') { - // Blur requires an island - var island_result = render_blur_island(node, camera, ctx, effect, parent_tint, parent_opacity, parent_scissor, parent_pos) - if (island_result) { - drawables.push(island_result) - } - return drawables + current_target = apply_bloom_effect(current_target, effect, ctx) + } else if (effect_type == 'mask') { + current_target = apply_mask_effect(current_target, effect, ctx) + } else if (effect_type == 'blur') { + current_target = apply_blur_effect(current_target, effect, ctx) } } - // Unknown effects - just render children normally - return collect_drawables(node, camera, parent_tint, parent_opacity, parent_scissor, parent_pos, ctx) + // 3. Return a single drawable that blits the final result + return [{ + type: 'blit_target', + layer: node.layer || 0, + world_y: 0, + target: current_target, + width: target_size.width, + height: target_size.height + }] } -// Render bloom effect as an island -function render_bloom_island(node, camera, ctx, effect, parent_tint, parent_opacity, parent_scissor, parent_pos) { - var backend = ctx.backend - var target_size = ctx.target_size - - // Allocate targets for the island - var content_target = backend.get_or_create_target(target_size.width, target_size.height, 'bloom_content_' + ctx.target_counter++) - var threshold_target = backend.get_or_create_target(target_size.width, target_size.height, 'bloom_threshold_' + ctx.target_counter++) - var blur_target_a = backend.get_or_create_target(target_size.width, target_size.height, 'bloom_blur_a_' + ctx.target_counter++) - var blur_target_b = backend.get_or_create_target(target_size.width, target_size.height, 'bloom_blur_b_' + ctx.target_counter++) - var output_target = backend.get_or_create_target(target_size.width, target_size.height, 'bloom_output_' + ctx.target_counter++) - - // Collect children drawables - var child_drawables = collect_children_drawables(node, camera, parent_tint, parent_opacity, parent_scissor, parent_pos, ctx) - - // Render children to content target - ctx.commands.push({cmd: 'begin_render', target: content_target, clear: {r: 0, g: 0, b: 0, a: 0}}) +// Helper: Render a list of drawables to a specific target +function render_drawables_to_target(drawables, target, camera, ctx) { + ctx.commands.push({cmd: 'begin_render', target: target, clear: {r: 0, g: 0, b: 0, a: 0}}) ctx.commands.push({cmd: 'set_camera', camera: camera}) - var batches = batch_drawables(child_drawables) + var batches = batch_drawables(drawables) for (var batch of batches) { if (batch.type == 'sprite_batch') { ctx.commands.push({ @@ -217,15 +186,42 @@ function render_bloom_island(node, camera, ctx, effect, parent_tint, parent_opac }) } else if (batch.type == 'text') { ctx.commands.push({cmd: 'draw_text', drawable: batch.drawable}) + } else if (batch.type == 'particles') { + ctx.commands.push({ + cmd: 'draw_batch', + batch_type: 'particles', + geometry: {sprites: batch.sprites}, + texture: batch.texture, + material: batch.material + }) + } else if (batch.type == 'blit_target') { + ctx.commands.push({ + cmd: 'blit', + texture: batch.drawable.target, + target: target, + dst_rect: {x: 0, y: 0, width: batch.drawable.width, height: batch.drawable.height}, + filter: 'linear' + }) } } ctx.commands.push({cmd: 'end_render'}) +} + +// Apply bloom effect to a source target, returning a new target +function apply_bloom_effect(src_target, effect, ctx) { + var backend = ctx.backend + var target_size = ctx.target_size + + var threshold_target = backend.get_or_create_target(target_size.width, target_size.height, 'bloom_threshold_' + ctx.target_counter++) + var blur_target_a = backend.get_or_create_target(target_size.width, target_size.height, 'bloom_blur_a_' + ctx.target_counter++) + var blur_target_b = backend.get_or_create_target(target_size.width, target_size.height, 'bloom_blur_b_' + ctx.target_counter++) + var output_target = backend.get_or_create_target(target_size.width, target_size.height, 'bloom_output_' + ctx.target_counter++) // Threshold pass ctx.commands.push({ cmd: 'shader_pass', shader: 'threshold', - input: content_target, + input: src_target, output: threshold_target, uniforms: {threshold: effect.threshold || 0.8, intensity: effect.intensity || 1.0} }) @@ -236,7 +232,6 @@ function render_bloom_island(node, camera, ctx, effect, parent_tint, parent_opac var blur_src = threshold_target for (var i = 0; i < blur_passes; i++) { - // Horizontal blur ctx.commands.push({ cmd: 'shader_pass', shader: 'blur', @@ -244,8 +239,6 @@ function render_bloom_island(node, camera, ctx, effect, parent_tint, parent_opac output: blur_target_a, uniforms: {direction: [2, 0], texel_size: texel_size} }) - - // Vertical blur ctx.commands.push({ cmd: 'shader_pass', shader: 'blur', @@ -253,146 +246,62 @@ function render_bloom_island(node, camera, ctx, effect, parent_tint, parent_opac output: blur_target_b, uniforms: {direction: [0, 2], texel_size: texel_size} }) - blur_src = blur_target_b } - // Composite bloom back onto content + // Composite bloom back onto source ctx.commands.push({ cmd: 'composite_textures', - base: content_target, + base: src_target, overlay: blur_src, output: output_target, mode: 'add' }) - // Return a drawable that blits the result - return { - type: 'blit_target', - layer: node.layer || 0, - world_y: 0, - target: output_target, - width: target_size.width, - height: target_size.height - } + return output_target } -// Render mask effect as an island -function render_mask_island(node, camera, ctx, effect, parent_tint, parent_opacity, parent_scissor, parent_pos) { +// Apply mask effect to a source target, returning a new target +function apply_mask_effect(src_target, effect, ctx) { var backend = ctx.backend var target_size = ctx.target_size + var camera = ctx.camera - // Allocate targets - var content_target = backend.get_or_create_target(target_size.width, target_size.height, 'mask_content_' + ctx.target_counter++) var mask_target = backend.get_or_create_target(target_size.width, target_size.height, 'mask_source_' + ctx.target_counter++) var output_target = backend.get_or_create_target(target_size.width, target_size.height, 'mask_output_' + ctx.target_counter++) - // Collect children drawables (the content to be masked) - var child_drawables = collect_children_drawables(node, camera, parent_tint, parent_opacity, parent_scissor, parent_pos, ctx) - - // Render children to content target - ctx.commands.push({cmd: 'begin_render', target: content_target, clear: {r: 0, g: 0, b: 0, a: 0}}) - ctx.commands.push({cmd: 'set_camera', camera: camera}) - - var batches = batch_drawables(child_drawables) - for (var batch of batches) { - if (batch.type == 'sprite_batch') { - ctx.commands.push({ - cmd: 'draw_batch', - batch_type: 'sprites', - geometry: {sprites: batch.sprites}, - texture: batch.texture, - material: batch.material - }) - } else if (batch.type == 'text') { - ctx.commands.push({cmd: 'draw_text', drawable: batch.drawable}) - } - } - ctx.commands.push({cmd: 'end_render'}) - // Render mask source var mask_source = effect.source if (mask_source) { var mask_drawables = collect_drawables(mask_source, camera, null, null, null, null, ctx) - - ctx.commands.push({cmd: 'begin_render', target: mask_target, clear: {r: 0, g: 0, b: 0, a: 0}}) - ctx.commands.push({cmd: 'set_camera', camera: camera}) - - var mask_batches = batch_drawables(mask_drawables) - for (var batch of mask_batches) { - if (batch.type == 'sprite_batch') { - ctx.commands.push({ - cmd: 'draw_batch', - batch_type: 'sprites', - geometry: {sprites: batch.sprites}, - texture: batch.texture, - material: batch.material - }) - } else if (batch.type == 'text') { - ctx.commands.push({cmd: 'draw_text', drawable: batch.drawable}) - } - } - ctx.commands.push({cmd: 'end_render'}) + render_drawables_to_target(mask_drawables, mask_target, camera, ctx) } // Apply mask ctx.commands.push({ cmd: 'apply_mask', - content_texture: content_target, + content_texture: src_target, mask_texture: mask_target, output: output_target, mode: effect.mode || 'alpha', invert: effect.invert || false }) - // Return a drawable that blits the result - return { - type: 'blit_target', - layer: node.layer || 0, - world_y: 0, - target: output_target, - width: target_size.width, - height: target_size.height - } + return output_target } -// Render blur effect as an island -function render_blur_island(node, camera, ctx, effect, parent_tint, parent_opacity, parent_scissor, parent_pos) { +// Apply blur effect to a source target, returning a new target +function apply_blur_effect(src_target, effect, ctx) { var backend = ctx.backend var target_size = ctx.target_size - // Allocate targets - var content_target = backend.get_or_create_target(target_size.width, target_size.height, 'blur_content_' + ctx.target_counter++) var blur_target_a = backend.get_or_create_target(target_size.width, target_size.height, 'blur_a_' + ctx.target_counter++) var blur_target_b = backend.get_or_create_target(target_size.width, target_size.height, 'blur_b_' + ctx.target_counter++) - // Collect children drawables - var child_drawables = collect_children_drawables(node, camera, parent_tint, parent_opacity, parent_scissor, parent_pos, ctx) - - // Render children to content target - ctx.commands.push({cmd: 'begin_render', target: content_target, clear: {r: 0, g: 0, b: 0, a: 0}}) - ctx.commands.push({cmd: 'set_camera', camera: camera}) - - var batches = batch_drawables(child_drawables) - for (var batch of batches) { - if (batch.type == 'sprite_batch') { - ctx.commands.push({ - cmd: 'draw_batch', - batch_type: 'sprites', - geometry: {sprites: batch.sprites}, - texture: batch.texture, - material: batch.material - }) - } else if (batch.type == 'text') { - ctx.commands.push({cmd: 'draw_text', drawable: batch.drawable}) - } - } - ctx.commands.push({cmd: 'end_render'}) - // Blur passes var blur_passes = effect.passes || 2 var texel_size = [1/target_size.width, 1/target_size.height] - var blur_src = content_target + var blur_src = src_target var blur_dst = blur_target_a for (var i = 0; i < blur_passes; i++) { @@ -424,15 +333,7 @@ function render_blur_island(node, camera, ctx, effect, parent_tint, parent_opaci blur_dst = tmp } - // Return a drawable that blits the result - return { - type: 'blit_target', - layer: node.layer || 0, - world_y: 0, - target: blur_src, - width: target_size.width, - height: target_size.height - } + return blur_src } // Collect drawables from children only (not the node itself)