752 lines
23 KiB
Plaintext
752 lines
23 KiB
Plaintext
// compositor.cm - Unified Compositor (Rewritten)
|
|
//
|
|
// The compositor is the SINGLE OWNER of all effects.
|
|
// It takes scene descriptions and produces abstract render plans.
|
|
//
|
|
// Architecture:
|
|
// Composition Tree (Structure) -> Compositor -> Render Plan -> Backend
|
|
//
|
|
// Changes:
|
|
// - No longer iterates scene/node trees.
|
|
// - Relies on 'resolve(ctx)' callbacks to get flat drawable lists.
|
|
// - Handles groups by filtering drawables and re-injecting texture_refs.
|
|
|
|
var effects_mod = use('effects')
|
|
|
|
var gpu = use('sdl_gpu')
|
|
|
|
var compositor = {}
|
|
|
|
// Presentation modes
|
|
compositor.PRESENTATION = {
|
|
DISABLED: 'disabled',
|
|
STRETCH: 'stretch',
|
|
LETTERBOX: 'letterbox',
|
|
OVERSCAN: 'overscan',
|
|
INTEGER_SCALE: 'integer_scale'
|
|
}
|
|
|
|
// Blend modes
|
|
compositor.BLEND = {
|
|
REPLACE: 'replace',
|
|
OVER: 'over',
|
|
ADD: 'add'
|
|
}
|
|
|
|
// Compile a composition tree into a render plan
|
|
compositor.compile = function(comp, renderers, backend = gpu) {
|
|
var ctx = {
|
|
renderers: renderers,
|
|
backend: backend,
|
|
passes: [],
|
|
targets: {},
|
|
persistent_targets: {},
|
|
target_counter: 0,
|
|
screen_size: null,
|
|
target_size: null,
|
|
|
|
// Target allocation
|
|
alloc_target: function(width, height, hint) {
|
|
var key = (hint || 'target') + '_' + text(this.target_counter++)
|
|
this.targets[key] = {width: width, height: height, key: key}
|
|
return {type: 'target', key: key, width: width, height: height}
|
|
},
|
|
|
|
// Persistent target (survives across frames)
|
|
get_persistent_target: function(key, width, height) {
|
|
if (!this.persistent_targets[key]) {
|
|
this.persistent_targets[key] = {width: width, height: height, key: key, persistent: true}
|
|
}
|
|
return {type: 'target', key: key, width: width, height: height, persistent: true}
|
|
},
|
|
|
|
// Helper to resolve drawables from a node (layer/plane)
|
|
resolve_drawables: function(node) {
|
|
if (node.drawables) return node.drawables
|
|
if (node.resolve) return node.resolve(this)
|
|
return []
|
|
}
|
|
}
|
|
|
|
// Get screen size from backend
|
|
ctx.screen_size = backend.get_window_size ? backend.get_window_size() : {width: 1280, height: 720}
|
|
|
|
// Compile the tree
|
|
_compile_node(comp, ctx, null, ctx.screen_size)
|
|
|
|
return {
|
|
passes: ctx.passes,
|
|
targets: ctx.targets,
|
|
persistent_targets: ctx.persistent_targets,
|
|
screen_size: ctx.screen_size
|
|
}
|
|
}
|
|
|
|
// Compile a single node
|
|
function _compile_node(node, ctx, parent_target, parent_size) {
|
|
if (!node) return {output: null}
|
|
|
|
var node_type = node.type
|
|
var owns_target = false
|
|
var target = parent_target
|
|
var target_size = {width: parent_size.width, height: parent_size.height}
|
|
|
|
// Determine target ownership
|
|
if (node.target == 'screen') {
|
|
owns_target = true
|
|
target = 'screen'
|
|
target_size = {width: ctx.screen_size.width, height: ctx.screen_size.height}
|
|
} else if (node.resolution) {
|
|
owns_target = true
|
|
target_size = {width: node.resolution.width, height: node.resolution.height}
|
|
target = ctx.alloc_target(target_size.width, target_size.height, node.name || 'res_target')
|
|
} else if (node.effects && _effects_require_target(node.effects, ctx)) {
|
|
owns_target = true
|
|
target = ctx.alloc_target(target_size.width, target_size.height, node.name || 'effect_target')
|
|
}
|
|
|
|
ctx.target_size = target_size
|
|
|
|
// Handle clear
|
|
if (owns_target && node.clear) {
|
|
ctx.passes.push({
|
|
type: 'clear',
|
|
target: target,
|
|
color: node.clear
|
|
})
|
|
}
|
|
|
|
// Process by type
|
|
if (node_type == 'composition') {
|
|
return _compile_composition(node, ctx, target, target_size)
|
|
} else if (node_type == 'group') {
|
|
return _compile_group_layer(node, ctx, target, target_size, parent_target, parent_size)
|
|
} else if (_is_renderer_type(node_type)) {
|
|
return _compile_renderer(node, ctx, target, target_size, parent_target, parent_size)
|
|
} else if (node_type == 'imgui') {
|
|
ctx.passes.push({
|
|
type: 'imgui',
|
|
target: target,
|
|
target_size: target_size,
|
|
draw: node.draw,
|
|
rect: node.rect
|
|
})
|
|
return {output: target}
|
|
}
|
|
|
|
return {output: target}
|
|
}
|
|
|
|
// Compile composition root
|
|
function _compile_composition(node, ctx, target, target_size) {
|
|
var layers = node.layers || []
|
|
|
|
for (var i = 0; i < layers.length; i++) {
|
|
var layer = layers[i]
|
|
if (!layer.blend) {
|
|
layer.blend = (i == 0) ? 'replace' : 'over'
|
|
}
|
|
_compile_node(layer, ctx, target, target_size)
|
|
}
|
|
|
|
return {output: target}
|
|
}
|
|
|
|
// Compile group layer (folder of layers)
|
|
function _compile_group_layer(node, ctx, target, target_size, parent_target, parent_size) {
|
|
var layers = node.layers || []
|
|
var original_target = target
|
|
|
|
// Process child layers
|
|
for (var i = 0; i < layers.length; i++) {
|
|
var layer = layers[i]
|
|
if (!layer.blend) {
|
|
layer.blend = (i == 0) ? 'replace' : 'over'
|
|
}
|
|
_compile_node(layer, ctx, target, target_size)
|
|
}
|
|
|
|
// Apply effects
|
|
if (node.effects && node.effects.length > 0) {
|
|
for (var j = 0; j < node.effects.length; j++) {
|
|
var effect = node.effects[j]
|
|
target = _compile_effect(effect, ctx, target, target_size, node.name || ('group_' + ctx.target_counter))
|
|
}
|
|
}
|
|
|
|
// Composite back to parent if needed
|
|
_composite_back(ctx, target, target_size, original_target, parent_target, parent_size, node)
|
|
|
|
return {output: target}
|
|
}
|
|
|
|
// Compile renderer layer (film2d, etc.)
|
|
function _compile_renderer(node, ctx, target, target_size, parent_target, parent_size) {
|
|
var renderer_type = node.type
|
|
var renderer = ctx.renderers[renderer_type]
|
|
|
|
if (!renderer) {
|
|
log.console(`compositor: Unknown renderer: ${renderer_type}`)
|
|
return {output: target}
|
|
}
|
|
|
|
var layer_target = target
|
|
var layer_size = target_size
|
|
var owns_target = false
|
|
|
|
// Check for resolution override
|
|
if (node.resolution) {
|
|
owns_target = true
|
|
layer_size = {width: node.resolution.width, height: node.resolution.height}
|
|
layer_target = ctx.alloc_target(layer_size.width, layer_size.height, node.name || renderer_type + '_target')
|
|
}
|
|
|
|
// Clear if we own target
|
|
if (owns_target && node.clear) {
|
|
ctx.passes.push({
|
|
type: 'clear',
|
|
target: layer_target,
|
|
color: node.clear
|
|
})
|
|
}
|
|
|
|
// 1. Resolve ALL drawables for this plane
|
|
var all_drawables = ctx.resolve_drawables(node)
|
|
|
|
// 2. Identify Group Memberships and Subtractions
|
|
var groups = node.groups || []
|
|
var consumed_indices = {} // Set of indices in all_drawables that are consumed
|
|
|
|
// If we have groups, we need to process them
|
|
for (var i = 0; i < groups.length; i++) {
|
|
var group = groups[i]
|
|
var group_drawables = []
|
|
|
|
// Resolve group selection
|
|
if (group.select) {
|
|
// Selector logic
|
|
/*
|
|
Simple selector: { tags: ['tag1'] } or { handles: [...] }
|
|
*/
|
|
if (group.select.tags) {
|
|
var tags = group.select.tags
|
|
for (var k = 0; k < all_drawables.length; k++) {
|
|
var d = all_drawables[k]
|
|
// Check if 'd' has any of the tags
|
|
// If 'd' is a handle, it has .tags array
|
|
// If 'd' is a struct, it might have .tags?
|
|
// Assuming all filterable items are handles for now or have tags prop
|
|
if (d.tags || (d.has_tag)) {
|
|
var match = false
|
|
for (var t = 0; t < tags.length; t++) {
|
|
if (d.has_tag && d.has_tag(tags[t])) { match = true; break }
|
|
if (d.tags && d.tags.indexOf && d.tags.indexOf(tags[t]) >= 0) { match = true; break }
|
|
}
|
|
if (match) {
|
|
group_drawables.push(d)
|
|
consumed_indices[k] = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Ask group to resolve itself if it has a custom callback?
|
|
if (group.resolve) {
|
|
// If group resolves explicit list, we need to match them to base list to remove them
|
|
// Or just trust the group list and try to remove by reference
|
|
var gd = group.resolve(ctx)
|
|
group_drawables = gd
|
|
// Mark as consumed by reference check? O(N*M) - risky.
|
|
// Assume ID check if handles.
|
|
for (var g = 0; g < gd.length; g++) {
|
|
var item = gd[g]
|
|
var id = item._id || item.id
|
|
if (id) {
|
|
for (var k = 0; k < all_drawables.length; k++) {
|
|
if (all_drawables[k]._id == id || all_drawables[k].id == id) {
|
|
consumed_indices[k] = true
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
// Ref equality fallback
|
|
var idx = all_drawables.indexOf(item)
|
|
if (idx >= 0) consumed_indices[idx] = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Render Group
|
|
if (group_drawables.length > 0) {
|
|
var group_target = ctx.alloc_target(layer_size.width, layer_size.height, (group.name||'group') + '_content')
|
|
var group_out = group_target
|
|
|
|
// Render content
|
|
ctx.passes.push({
|
|
type: 'render',
|
|
renderer: renderer_type,
|
|
drawables: group_drawables,
|
|
camera: node.camera,
|
|
target: group_target,
|
|
target_size: layer_size,
|
|
blend: 'replace',
|
|
clear: {r:0,g:0,b:0,a:0}
|
|
})
|
|
|
|
// Apply effects
|
|
if (group.effects) {
|
|
for (var e = 0; e < group.effects.length; e++) {
|
|
group_out = _compile_effect(group.effects[e], ctx, group_out, layer_size, group.name)
|
|
}
|
|
}
|
|
|
|
// Insert result as texture_ref into MAIN list
|
|
// We need to inject it. We can add it to 'all_drawables' but mark it as NOT consumed?
|
|
// No, 'all_drawables' iteration is done.
|
|
// We'll construct the 'final_drawables' list.
|
|
|
|
// Create texture_ref drawable.
|
|
// Note: we can't create a 'handle' easily here without film2d's help, BUT film2d.render accepts raw structs.
|
|
var tex_ref = {
|
|
type: 'texture_ref',
|
|
texture_target: group_out,
|
|
pos: {x: 0, y: 0}, // Full screen quad usually? Or group bounds? User: "Ship full-plane first".
|
|
width: layer_size.width,
|
|
height: layer_size.height,
|
|
layer: group.output_layer || 0,
|
|
blend: 'over', // Textures usually blend over
|
|
world_y: (group.pos ? group.pos.y : 0) // Sorting? "Group is atomic in ordering... Insert at output_layer"
|
|
}
|
|
|
|
// We defer adding this to the final list until after filtering
|
|
group.generated_ref = tex_ref
|
|
}
|
|
}
|
|
|
|
// 3. Construct Final List
|
|
var final_drawables = []
|
|
for (var k = 0; k < all_drawables.length; k++) {
|
|
if (!consumed_indices[k]) {
|
|
final_drawables.push(all_drawables[k])
|
|
}
|
|
}
|
|
|
|
// Add generated refs
|
|
for (var i = 0; i < groups.length; i++) {
|
|
if (groups[i].generated_ref) {
|
|
final_drawables.push(groups[i].generated_ref)
|
|
}
|
|
}
|
|
|
|
// 4. Main Render Pass
|
|
ctx.passes.push({
|
|
type: 'render',
|
|
renderer: renderer_type,
|
|
drawables: final_drawables,
|
|
camera: node.camera,
|
|
target: layer_target,
|
|
target_size: layer_size,
|
|
blend: node.blend || 'over',
|
|
clear: owns_target ? node.clear : null
|
|
})
|
|
|
|
// Composite back to parent
|
|
_composite_back(ctx, layer_target, layer_size, target, parent_target, parent_size, node)
|
|
|
|
return {output: layer_target}
|
|
}
|
|
|
|
function _composite_back(ctx, current_target, current_size, original_target, parent_target, parent_size, node) {
|
|
var needs_composite = (original_target != parent_target || current_target != original_target) && parent_target
|
|
|
|
if (needs_composite) {
|
|
var presentation = node.presentation || 'disabled'
|
|
var blend = node.blend || 'over'
|
|
|
|
if (parent_target == 'screen') {
|
|
ctx.passes.push({
|
|
type: 'blit_to_screen',
|
|
source: current_target,
|
|
source_size: current_size,
|
|
dest_size: parent_size,
|
|
presentation: presentation,
|
|
pos: node.pos
|
|
})
|
|
} else {
|
|
ctx.passes.push({
|
|
type: 'composite',
|
|
source: current_target,
|
|
dest: parent_target,
|
|
source_size: current_size,
|
|
dest_size: parent_size,
|
|
presentation: presentation,
|
|
blend: blend,
|
|
pos: node.pos
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compile an effect using the effect registry
|
|
function _compile_effect(effect, ctx, input_target, target_size, node_id) {
|
|
var effect_type = effect.type
|
|
var effect_def = effects_mod.get(effect_type)
|
|
|
|
if (!effect_def) {
|
|
log.console(`compositor: Unknown effect: ${effect_type}`)
|
|
return input_target
|
|
}
|
|
|
|
// Build effect context
|
|
var effect_ctx = {
|
|
backend: ctx.backend,
|
|
target_size: {width: target_size.width, height: target_size.height},
|
|
alloc_target: function(width, height, hint) {
|
|
return ctx.alloc_target(width, height, hint)
|
|
},
|
|
get_persistent_target: function(key, width, height) {
|
|
return ctx.get_persistent_target(node_id + '_' + key, width, height)
|
|
},
|
|
// Allows effects to resolve sources (for masks)
|
|
resolve_source: function(source_def) {
|
|
// If source is "tags", iterate all drawables of the current renderer?
|
|
// This is tricky. Masks need access to the plane's drawables.
|
|
// Simplified: The mask effect in 'paladin.ce' has a 'source' handle directly.
|
|
// We can wrap it in a drawable list.
|
|
if (source_def.type == 'handle') {
|
|
return [source_def]
|
|
}
|
|
if (source_def.tags) {
|
|
// We don't have easy access to the full list here unless we passed it.
|
|
// For now, assume mask sources are explicit handles or handle lists passed in the effect config.
|
|
return []
|
|
}
|
|
// If source is a handle object (has _id)
|
|
if (source_def._id || source_def.type) return [source_def]
|
|
return []
|
|
}
|
|
}
|
|
|
|
// Allocate output target
|
|
var output = ctx.alloc_target(target_size.width, target_size.height, effect_type + '_out')
|
|
|
|
// Build effect passes
|
|
var effect_passes = effect_def.build_passes(input_target, output, effect, effect_ctx)
|
|
|
|
// Convert effect passes to compositor passes
|
|
for (var i = 0; i < effect_passes.length; i++) {
|
|
var ep = effect_passes[i]
|
|
ctx.passes.push(_convert_effect_pass(ep, ctx))
|
|
}
|
|
|
|
return output
|
|
}
|
|
|
|
// Convert effect pass to compositor pass format
|
|
function _convert_effect_pass(ep, ctx) {
|
|
switch (ep.type) {
|
|
case 'shader':
|
|
// Handle both single input and multiple inputs array
|
|
var primary_input = ep.input
|
|
var extra = []
|
|
|
|
if (is_array(ep.inputs) && ep.inputs.length > 0) {
|
|
if (!primary_input) primary_input = ep.inputs[0]
|
|
extra = ep.inputs.slice(1)
|
|
}
|
|
return {
|
|
type: 'shader_pass',
|
|
shader: ep.shader,
|
|
input: primary_input,
|
|
extra_inputs: extra,
|
|
output: ep.output,
|
|
uniforms: ep.uniforms
|
|
}
|
|
case 'composite':
|
|
return {
|
|
type: 'composite_textures',
|
|
base: ep.base,
|
|
overlay: ep.overlay,
|
|
output: ep.output,
|
|
mode: ep.blend || 'over'
|
|
}
|
|
case 'blit':
|
|
return {
|
|
type: 'composite',
|
|
source: ep.source,
|
|
dest: ep.dest,
|
|
source_size: ep.source.width ? {width: ep.source.width, height: ep.source.height} : ctx.target_size,
|
|
dest_size: ep.dest.width ? {width: ep.dest.width, height: ep.dest.height} : ctx.target_size,
|
|
presentation: 'disabled'
|
|
}
|
|
case 'render_subtree':
|
|
// This is now "render_drawables"
|
|
// Ensure drawables is an array because film2d expects array
|
|
var list = ep.root
|
|
if (!list) list = []
|
|
else if (!is_array(list)) list = [list]
|
|
|
|
return {
|
|
type: 'render_mask_source',
|
|
drawables: list,
|
|
target: ep.output,
|
|
target_size: {width: ep.output.width, height: ep.output.height},
|
|
space: ep.space || 'local'
|
|
}
|
|
default:
|
|
return ep
|
|
}
|
|
}
|
|
|
|
// Check if effects require a target
|
|
function _effects_require_target(effects, ctx) {
|
|
for (var i = 0; i < effects.length; i++) {
|
|
var effect = effects[i]
|
|
if (effects_mod.requires_target(effect.type)) return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Check if type is a renderer
|
|
function _is_renderer_type(type) {
|
|
return type == 'film2d' || type == 'forward3d' || type == 'retro3d' || type == 'simple2d'
|
|
}
|
|
|
|
// Calculate presentation rect
|
|
compositor.calculate_presentation_rect = function(source_size, dest_size, mode) {
|
|
var sw = source_size.width
|
|
var sh = source_size.height
|
|
var dw = dest_size.width
|
|
var dh = dest_size.height
|
|
|
|
if (mode == 'disabled') {
|
|
return {x: 0, y: 0, width: number.min(sw, dw), height: number.min(sh, dh)}
|
|
}
|
|
if (mode == 'stretch') {
|
|
return {x: 0, y: 0, width: dw, height: dh}
|
|
}
|
|
|
|
var src_aspect = sw / sh
|
|
var dst_aspect = dw / dh
|
|
|
|
if (mode == 'letterbox') {
|
|
var scale = src_aspect > dst_aspect ? dw / sw : dh / sh
|
|
var w = sw * scale
|
|
var h = sh * scale
|
|
return {x: (dw - w) / 2, y: (dh - h) / 2, width: w, height: h}
|
|
}
|
|
|
|
if (mode == 'overscan') {
|
|
var scale = src_aspect > dst_aspect ? dh / sh : dw / sw
|
|
var w = sw * scale
|
|
var h = sh * scale
|
|
return {x: (dw - w) / 2, y: (dh - h) / 2, width: w, height: h}
|
|
}
|
|
|
|
if (mode == 'integer_scale') {
|
|
var scale_x = number.floor(dw / sw)
|
|
var scale_y = number.floor(dh / sh)
|
|
var scale = number.max(1, number.min(scale_x, scale_y))
|
|
var w = sw * scale
|
|
var h = sh * scale
|
|
return {x: (dw - w) / 2, y: (dh - h) / 2, width: w, height: h}
|
|
}
|
|
|
|
return {x: 0, y: 0, width: sw, height: sh}
|
|
}
|
|
|
|
// Execute a compiled render plan
|
|
compositor.execute = function(plan, renderers, backend = gpu) {
|
|
var target_cache = {}
|
|
|
|
// Pre-allocate targets
|
|
for (var key in plan.targets) {
|
|
var spec = plan.targets[key]
|
|
target_cache[key] = backend.get_or_create_target(spec.width, spec.height, key)
|
|
}
|
|
|
|
for (var key in plan.persistent_targets) {
|
|
var spec = plan.persistent_targets[key]
|
|
if (!target_cache[key]) {
|
|
target_cache[key] = backend.get_or_create_target(spec.width, spec.height, key)
|
|
}
|
|
}
|
|
|
|
// Resolve target references
|
|
function resolve_target(t) {
|
|
if (t == 'screen') return 'screen'
|
|
if (t && t.type == 'target') return target_cache[t.key]
|
|
return t
|
|
}
|
|
|
|
// Execute passes
|
|
var commands = []
|
|
|
|
for (var i = 0; i < plan.passes.length; i++) {
|
|
var pass = plan.passes[i]
|
|
var pass_cmds = _execute_pass(pass, renderers, backend, resolve_target, compositor)
|
|
for (var j = 0; j < pass_cmds.length; j++) {
|
|
commands.push(pass_cmds[j])
|
|
}
|
|
}
|
|
|
|
return {commands: commands}
|
|
}
|
|
|
|
// Execute a single pass
|
|
function _execute_pass(pass, renderers, backend, resolve_target, comp) {
|
|
var commands = []
|
|
var source
|
|
|
|
switch (pass.type) {
|
|
case 'clear':
|
|
var target = resolve_target(pass.target)
|
|
commands.push({cmd: 'begin_render', target: target, clear: pass.color})
|
|
commands.push({cmd: 'end_render'})
|
|
break
|
|
|
|
case 'render':
|
|
var renderer = renderers[pass.renderer]
|
|
if (renderer && renderer.render) {
|
|
var target = resolve_target(pass.target)
|
|
// Pass drawables directly
|
|
var result = renderer.render({
|
|
drawables: pass.drawables,
|
|
camera: pass.camera,
|
|
target: target,
|
|
target_size: pass.target_size,
|
|
blend: pass.blend,
|
|
clear: pass.clear
|
|
}, backend)
|
|
|
|
if (result && result.commands) {
|
|
for (var i = 0; i < result.commands.length; i++) {
|
|
commands.push(result.commands[i])
|
|
}
|
|
}
|
|
}
|
|
break
|
|
|
|
case 'shader_pass':
|
|
var input = resolve_target(pass.input)
|
|
var output = resolve_target(pass.output)
|
|
var extra = []
|
|
if (pass.extra_inputs) {
|
|
for (var i = 0; i < pass.extra_inputs.length; i++) {
|
|
extra.push(resolve_target(pass.extra_inputs[i]))
|
|
}
|
|
}
|
|
commands.push({
|
|
cmd: 'shader_pass',
|
|
shader: pass.shader,
|
|
input: input,
|
|
output: output,
|
|
extra_inputs: extra,
|
|
uniforms: pass.uniforms
|
|
})
|
|
break
|
|
|
|
case 'composite': {
|
|
source = resolve_target(pass.source)
|
|
var dest = resolve_target(pass.dest)
|
|
var rect = comp.calculate_presentation_rect(pass.source_size, pass.dest_size, pass.presentation)
|
|
if (pass.pos) {
|
|
rect.x += (pass.pos.x || 0)
|
|
rect.y += (pass.pos.y || 0)
|
|
}
|
|
var filter = pass.presentation == 'integer_scale' ? 'nearest' : 'linear'
|
|
commands.push({
|
|
cmd: 'blit',
|
|
texture: source,
|
|
target: dest,
|
|
dst_rect: rect,
|
|
filter: filter
|
|
})
|
|
}
|
|
break
|
|
|
|
case 'blit_to_screen': {
|
|
source = resolve_target(pass.source)
|
|
var rect = comp.calculate_presentation_rect(pass.source_size, pass.dest_size, pass.presentation)
|
|
if (pass.pos) {
|
|
rect.x += (pass.pos.x || 0)
|
|
rect.y += (pass.pos.y || 0)
|
|
}
|
|
var filter = pass.presentation == 'integer_scale' ? 'nearest' : 'linear'
|
|
commands.push({
|
|
cmd: 'blit',
|
|
texture: source,
|
|
target: 'screen',
|
|
dst_rect: rect,
|
|
filter: filter
|
|
})
|
|
}
|
|
break
|
|
|
|
case 'composite_textures': {
|
|
var base = resolve_target(pass.base)
|
|
var overlay = resolve_target(pass.overlay)
|
|
var output = resolve_target(pass.output)
|
|
commands.push({
|
|
cmd: 'composite_textures',
|
|
base: base,
|
|
overlay: overlay,
|
|
output: output,
|
|
mode: pass.mode || 'over'
|
|
})
|
|
}
|
|
break
|
|
|
|
case 'apply_mask': {
|
|
var content = resolve_target(pass.content)
|
|
var mask = resolve_target(pass.mask)
|
|
var output = resolve_target(pass.output)
|
|
commands.push({
|
|
cmd: 'apply_mask',
|
|
content_texture: content,
|
|
mask_texture: mask,
|
|
output: output,
|
|
mode: pass.mode,
|
|
invert: pass.invert
|
|
})
|
|
}
|
|
break
|
|
|
|
case 'imgui': {
|
|
commands.push({
|
|
cmd: 'imgui',
|
|
draw: pass.draw,
|
|
rect: pass.rect,
|
|
target: resolve_target(pass.target)
|
|
})
|
|
}
|
|
break
|
|
|
|
case 'render_mask_source': {
|
|
var target = resolve_target(pass.target)
|
|
var renderer = renderers['film2d'] // Assume film2d for now
|
|
if (renderer && renderer.render) {
|
|
var result = renderer.render({
|
|
drawables: pass.drawables, // Was 'root'
|
|
camera: {pos: {x: 0, y: 0}, width: pass.target_size.width, height: pass.target_size.height, anchor: {x: 0, y: 0}, ortho: true},
|
|
target: target,
|
|
target_size: pass.target_size,
|
|
blend: 'replace',
|
|
clear: {r: 0, g: 0, b: 0, a: 0}
|
|
}, backend)
|
|
if (result && result.commands) {
|
|
for (var i = 0; i < result.commands.length; i++) {
|
|
commands.push(result.commands[i])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break
|
|
}
|
|
|
|
return commands
|
|
}
|
|
|
|
return compositor
|