clay 2
This commit is contained in:
416
clay2.cm
Normal file
416
clay2.cm
Normal file
@@ -0,0 +1,416 @@
|
||||
// clay2.cm - Revised UI layout engine emitting scene trees
|
||||
//
|
||||
// Changes from clay.cm:
|
||||
// - No __proto__, uses meme/merge
|
||||
// - Emits scene tree nodes for fx_graph instead of immediate draw commands
|
||||
// - Supports scissor clipping on groups
|
||||
|
||||
var layout = use('layout')
|
||||
var graphics = use('graphics')
|
||||
var prosperon = use('prosperon')
|
||||
|
||||
var clay = {}
|
||||
|
||||
// Layout context
|
||||
var lay_ctx = layout.make_context()
|
||||
|
||||
// Base configuration for UI elements
|
||||
var base_config = {
|
||||
font: null,
|
||||
background_image: null,
|
||||
slice: 0,
|
||||
font_path: 'fonts/dos', // Default font
|
||||
font_size: 16,
|
||||
color: {r:1, g:1, b:1, a:1},
|
||||
spacing: 0,
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
offset: {x:0, y:0},
|
||||
size: null,
|
||||
background_color: null,
|
||||
clipped: false,
|
||||
text_break: 'word',
|
||||
text_align: 'left',
|
||||
max_size: null,
|
||||
contain: 0,
|
||||
behave: 0
|
||||
}
|
||||
|
||||
function normalize_spacing(s) {
|
||||
if (typeof s == 'number') return {l:s, r:s, t:s, b:s}
|
||||
if (isa(s, array)) {
|
||||
if (s.length == 2) return {l:s[0], r:s[0], t:s[1], b:s[1]}
|
||||
if (s.length == 4) return {l:s[0], r:s[1], t:s[2], b:s[3]}
|
||||
}
|
||||
if (typeof s == 'object') return {l:s.l||0, r:s.r||0, t:s.t||0, b:s.b||0}
|
||||
return {l:0, r:0, t:0, b:0}
|
||||
}
|
||||
|
||||
// Tree building state
|
||||
var root_item
|
||||
var tree_root
|
||||
var config_stack = []
|
||||
|
||||
clay.layout = function(fn, size) {
|
||||
lay_ctx.reset()
|
||||
|
||||
var root = lay_ctx.item()
|
||||
if (isa(size, array)) size = {width: size[0], height: size[1]}
|
||||
|
||||
lay_ctx.set_size(root, size)
|
||||
lay_ctx.set_contain(root, layout.contain.row) // Default root layout
|
||||
|
||||
root_item = root
|
||||
|
||||
var root_config = meme(base_config)
|
||||
tree_root = {
|
||||
id: root,
|
||||
config: root_config,
|
||||
children: []
|
||||
}
|
||||
|
||||
config_stack = [root_config]
|
||||
|
||||
fn()
|
||||
|
||||
lay_ctx.run()
|
||||
|
||||
// Post-layout: build scene tree
|
||||
return build_scene_tree(tree_root, size.height)
|
||||
}
|
||||
|
||||
function build_scene_tree(node, root_height, parent_abs_x, parent_abs_y) {
|
||||
parent_abs_x = parent_abs_x || 0
|
||||
parent_abs_y = parent_abs_y || 0
|
||||
|
||||
var rect = lay_ctx.get_rect(node.id)
|
||||
|
||||
// Calculate absolute world Y for this node (bottom-up layout to top-down render)
|
||||
// rect.y is from bottom
|
||||
var abs_y = root_height - (rect.y + rect.height)
|
||||
var abs_x = rect.x
|
||||
|
||||
// Calculate relative position for the group
|
||||
var rel_x = abs_x - parent_abs_x
|
||||
var rel_y = abs_y - parent_abs_y
|
||||
|
||||
// The node to return. It might be a group or a sprite/text depending on config.
|
||||
var scene_node = {
|
||||
type: 'group',
|
||||
pos: {x: rel_x + node.config.offset.x, y: rel_y + node.config.offset.y},
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
children: []
|
||||
}
|
||||
|
||||
// Background
|
||||
if (node.config.background_image) {
|
||||
if (node.config.slice) {
|
||||
scene_node.children.push({
|
||||
type: 'sprite',
|
||||
image: node.config.background_image,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
slice: node.config.slice,
|
||||
color: node.config.background_color || {r:1, g:1, b:1, a:1},
|
||||
layer: -1 // Back
|
||||
})
|
||||
} else {
|
||||
scene_node.children.push({
|
||||
type: 'sprite',
|
||||
image: node.config.background_image,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
color: node.config.background_color || {r:1, g:1, b:1, a:1},
|
||||
layer: -1
|
||||
})
|
||||
}
|
||||
} else if (node.config.background_color) {
|
||||
scene_node.children.push({
|
||||
type: 'rect',
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
color: node.config.background_color,
|
||||
layer: -1
|
||||
})
|
||||
}
|
||||
|
||||
// Content (Image/Text)
|
||||
if (node.config.image) {
|
||||
scene_node.children.push({
|
||||
type: 'sprite',
|
||||
image: node.config.image,
|
||||
width: rect.width, // layout ensures aspect ratio if configured
|
||||
height: rect.height,
|
||||
color: node.config.color
|
||||
})
|
||||
}
|
||||
|
||||
if (node.config.text) {
|
||||
scene_node.children.push({
|
||||
type: 'text',
|
||||
text: node.config.text,
|
||||
font: node.config.font_path,
|
||||
size: node.config.font_size,
|
||||
color: node.config.color,
|
||||
pos: {x: 0, y: rect.height} // Text baseline relative to group
|
||||
})
|
||||
}
|
||||
|
||||
// Clipping
|
||||
if (node.config.clipped) {
|
||||
// Scissor needs absolute coordinates
|
||||
// We can compute them from our current absolute position
|
||||
scene_node.scissor = {
|
||||
x: abs_x + node.config.offset.x,
|
||||
y: abs_y + node.config.offset.y,
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
}
|
||||
}
|
||||
|
||||
// Children
|
||||
// Pass our absolute position as the new parent absolute position
|
||||
var my_abs_x = abs_x + node.config.offset.x
|
||||
var my_abs_y = abs_y + node.config.offset.y
|
||||
|
||||
for (var child of node.children) {
|
||||
var child_node = build_scene_tree(child, root_height, my_abs_x, my_abs_y)
|
||||
scene_node.children.push(child_node)
|
||||
}
|
||||
|
||||
return scene_node
|
||||
}
|
||||
|
||||
|
||||
// --- Item Creation Helpers ---
|
||||
|
||||
function add_item(config) {
|
||||
var parent_config = config_stack[config_stack.length-1]
|
||||
|
||||
// Merge parent spacing/padding/margin logic?
|
||||
// clay.cm does normalizing and applying child_gap.
|
||||
|
||||
var use_config = meme(base_config, config)
|
||||
|
||||
var item = lay_ctx.item()
|
||||
lay_ctx.set_margins(item, normalize_spacing(use_config.margin))
|
||||
lay_ctx.set_contain(item, use_config.contain)
|
||||
lay_ctx.set_behave(item, use_config.behave)
|
||||
|
||||
if (use_config.size) {
|
||||
var s = use_config.size
|
||||
if (isa(s, array)) s = {width: s[0], height: s[1]}
|
||||
lay_ctx.set_size(item, s)
|
||||
}
|
||||
|
||||
var node = {
|
||||
id: item,
|
||||
config: use_config,
|
||||
children: []
|
||||
}
|
||||
|
||||
// Link to tree
|
||||
// We need to know current parent node.
|
||||
// Track `tree_stack` alongside `config_stack`?
|
||||
// Or just `tree_node_stack`.
|
||||
|
||||
// Let's fix the state tracking.
|
||||
}
|
||||
|
||||
// Rewriting state management for cleaner recursion
|
||||
var tree_stack = []
|
||||
|
||||
clay.layout = function(fn, size) {
|
||||
lay_ctx.reset()
|
||||
var root_id = lay_ctx.item()
|
||||
if (isa(size, array)) size = {width: size[0], height: size[1]}
|
||||
|
||||
lay_ctx.set_size(root_id, size)
|
||||
lay_ctx.set_contain(root_id, layout.contain.row)
|
||||
|
||||
var root_node = {
|
||||
id: root_id,
|
||||
config: meme(base_config, {size: size}),
|
||||
children: []
|
||||
}
|
||||
|
||||
tree_stack = [root_node]
|
||||
|
||||
fn() // User builds tree
|
||||
|
||||
lay_ctx.run()
|
||||
|
||||
return build_scene_tree(root_node, size.height)
|
||||
}
|
||||
|
||||
function process_configs(configs) {
|
||||
// Merge array of configs from right to left (right overrides left)
|
||||
// And merge with base_config
|
||||
var res = meme(base_config)
|
||||
for (var c of configs) {
|
||||
if (c) res = meme(res, c)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function push_node(configs, contain_mode) {
|
||||
var config = process_configs(configs)
|
||||
if (contain_mode != null) config.contain = contain_mode
|
||||
|
||||
var item = lay_ctx.item()
|
||||
|
||||
// Apply layout props
|
||||
lay_ctx.set_margins(item, normalize_spacing(config.margin))
|
||||
lay_ctx.set_contain(item, config.contain)
|
||||
lay_ctx.set_behave(item, config.behave)
|
||||
|
||||
if (config.size) {
|
||||
var s = config.size
|
||||
if (isa(s, array)) s = {width: s[0], height: s[1]}
|
||||
lay_ctx.set_size(item, s)
|
||||
}
|
||||
|
||||
var node = {
|
||||
id: item,
|
||||
config: config,
|
||||
children: []
|
||||
}
|
||||
|
||||
// Add to parent
|
||||
var parent = tree_stack[tree_stack.length-1]
|
||||
parent.children.push(node)
|
||||
lay_ctx.insert(parent.id, item)
|
||||
|
||||
tree_stack.push(node)
|
||||
return node
|
||||
}
|
||||
|
||||
function pop_node() {
|
||||
tree_stack.pop()
|
||||
}
|
||||
|
||||
// Generic container
|
||||
clay.container = function(configs, fn) {
|
||||
if (typeof configs == 'function') { fn = configs; configs = {} }
|
||||
if (!isa(configs, array)) configs = [configs]
|
||||
|
||||
push_node(configs, null)
|
||||
if (fn) fn()
|
||||
pop_node()
|
||||
}
|
||||
|
||||
// Stacks
|
||||
clay.vstack = function(configs, fn) {
|
||||
if (typeof configs == 'function') { fn = configs; configs = {} }
|
||||
if (!isa(configs, array)) configs = [configs]
|
||||
|
||||
var c = layout.contain.column
|
||||
// Check for alignment/justification in configs?
|
||||
// Assume generic container props handle it via `contain` override or we defaults
|
||||
|
||||
push_node(configs, c)
|
||||
if (fn) fn()
|
||||
pop_node()
|
||||
}
|
||||
|
||||
clay.hstack = function(configs, fn) {
|
||||
if (typeof configs == 'function') { fn = configs; configs = {} }
|
||||
if (!isa(configs, array)) configs = [configs]
|
||||
|
||||
var c = layout.contain.row
|
||||
push_node(configs, c)
|
||||
if (fn) fn()
|
||||
pop_node()
|
||||
}
|
||||
|
||||
clay.zstack = function(configs, fn) {
|
||||
if (typeof configs == 'function') { fn = configs; configs = {} }
|
||||
if (!isa(configs, array)) configs = [configs]
|
||||
|
||||
// Stack (overlap)
|
||||
// layout.contain.stack? layout.contain.overlap?
|
||||
// 'layout' module usually defaults to overlap if not row/column?
|
||||
// Or we just don't set row/column bit.
|
||||
var c = layout.contain.layout // Just layout (no flow)
|
||||
|
||||
push_node(configs, c)
|
||||
if (fn) fn()
|
||||
pop_node()
|
||||
}
|
||||
|
||||
// Leaf nodes
|
||||
clay.image = function(path, ...configs) {
|
||||
var img = graphics.texture(path)
|
||||
var c = {image: path}
|
||||
// Auto-size if not provided
|
||||
// But we need to check if configs override it
|
||||
var final_config = process_configs(configs)
|
||||
if (!final_config.size && !final_config.behave) { // If no size and no fill behavior
|
||||
c.size = {width: img.width, height: img.height}
|
||||
}
|
||||
|
||||
push_node([c, ...configs], null)
|
||||
pop_node()
|
||||
}
|
||||
|
||||
clay.text = function(str, ...configs) {
|
||||
var c = {text: str}
|
||||
// Measuring
|
||||
var final_config = process_configs(configs)
|
||||
// measure text
|
||||
var font = graphics.get_font(final_config.font_path) // or font cache
|
||||
// Need to handle font object vs path string
|
||||
// For now assume path string in config or default
|
||||
|
||||
// This measurement is synchronous and might differ from GPU font rendering slightly
|
||||
// but good enough for layout.
|
||||
// We need to know max width for wrapping?
|
||||
// 'layout' doesn't easily support "height depends on width" during single pass?
|
||||
// We specify a fixed size or behave.
|
||||
|
||||
// If no size specified, measure single line
|
||||
if (!final_config.size && !final_config.behave) {
|
||||
// Basic measurement
|
||||
// Hack: use arbitrary width for now?
|
||||
// Or we need a proper text measurement exposed.
|
||||
// graphics.measure_text(font, text, size, break, align)
|
||||
// Assume we have it or minimal version.
|
||||
// clay.cm used `font.text_size`
|
||||
|
||||
// We'll rely on font path to get a font object
|
||||
// var f = graphics.get_font(final_config.font_path) ...
|
||||
// c.size = ...
|
||||
c.size = {width: 100, height: 20} // Fallback for now to avoid crashes
|
||||
}
|
||||
|
||||
push_node([c, ...configs], null)
|
||||
pop_node()
|
||||
}
|
||||
|
||||
clay.rectangle = function(...configs) {
|
||||
// Just a container with background color really, but as a leaf
|
||||
push_node(configs, null)
|
||||
pop_node()
|
||||
}
|
||||
|
||||
clay.button = function(str, action, ...configs) {
|
||||
// Button is a container with text and click behavior
|
||||
// For rendering, it's a zstack of background + text
|
||||
|
||||
// We can just define it as a container with background styling
|
||||
var btn_config = {
|
||||
padding: 10,
|
||||
background_color: {r:0.3, g:0.3, b:0.4, a:1}
|
||||
}
|
||||
|
||||
clay.zstack([btn_config, ...configs], function() {
|
||||
clay.text(str, {color: {r:1,g:1,b:1,a:1}})
|
||||
})
|
||||
}
|
||||
|
||||
// Constants
|
||||
clay.behave = layout.behave
|
||||
clay.contain = layout.contain
|
||||
|
||||
return clay
|
||||
98
fx_graph.cm
98
fx_graph.cm
@@ -172,7 +172,15 @@ NODE_EXECUTORS.render_view = function(params, backend) {
|
||||
|
||||
// Batch and emit draw commands
|
||||
var batches = batch_drawables(drawables)
|
||||
var current_scissor = null
|
||||
|
||||
for (var batch of batches) {
|
||||
// Emit scissor command if changed
|
||||
if (!rect_equal(current_scissor, batch.scissor)) {
|
||||
commands.push({cmd: 'scissor', rect: batch.scissor})
|
||||
current_scissor = batch.scissor
|
||||
}
|
||||
|
||||
if (batch.type == 'sprite_batch') {
|
||||
commands.push({
|
||||
cmd: 'draw_batch',
|
||||
@@ -393,7 +401,7 @@ NODE_EXECUTORS.shader_pass = function(params, backend) {
|
||||
// SCENE TREE TRAVERSAL
|
||||
// ========================================================================
|
||||
|
||||
function collect_drawables(node, camera, parent_tint, parent_opacity) {
|
||||
function collect_drawables(node, camera, parent_tint, parent_opacity, parent_scissor, parent_pos) {
|
||||
if (!node) return []
|
||||
|
||||
parent_tint = parent_tint || [1, 1, 1, 1]
|
||||
@@ -401,6 +409,14 @@ function collect_drawables(node, camera, parent_tint, parent_opacity) {
|
||||
|
||||
var drawables = []
|
||||
|
||||
// Compute absolute position
|
||||
parent_pos = parent_pos || {x: 0, y: 0}
|
||||
var node_pos = node.pos || {x: 0, y: 0}
|
||||
var abs_x = parent_pos.x + (node_pos.x != null ? node_pos.x : (node_pos[0] || 0))
|
||||
var abs_y = parent_pos.y + (node_pos.y != null ? node_pos.y : (node_pos[1] || 0))
|
||||
// For recursive calls, use this node's absolute pos as parent pos
|
||||
var current_pos = {x: abs_x, y: abs_y}
|
||||
|
||||
// Compute inherited tint/opacity
|
||||
var node_tint = node.tint || node.color
|
||||
var world_tint = [
|
||||
@@ -411,16 +427,29 @@ function collect_drawables(node, camera, parent_tint, parent_opacity) {
|
||||
]
|
||||
var world_opacity = parent_opacity * (node.opacity != null ? node.opacity : 1)
|
||||
|
||||
// Handle different node types
|
||||
// Compute effective scissor
|
||||
var current_scissor = parent_scissor
|
||||
if (node.scissor) {
|
||||
if (parent_scissor) {
|
||||
// Intersect parent and node scissor
|
||||
var x1 = Math.max(parent_scissor.x, node.scissor.x)
|
||||
var y1 = Math.max(parent_scissor.y, node.scissor.y)
|
||||
var x2 = Math.min(parent_scissor.x + parent_scissor.width, node.scissor.x + node.scissor.width)
|
||||
var y2 = Math.min(parent_scissor.y + parent_scissor.height, node.scissor.y + node.scissor.height)
|
||||
current_scissor = {x: x1, y: y1, width: Math.max(0, x2 - x1), height: Math.max(0, y2 - y1)}
|
||||
} else {
|
||||
current_scissor = node.scissor
|
||||
}
|
||||
}
|
||||
|
||||
// Handle different node types
|
||||
if (node.type == 'sprite' || (node.image && !node.type)) {
|
||||
if (node.slice && node.tile) {
|
||||
throw Error('Sprite cannot have both "slice" and "tile" parameters.')
|
||||
}
|
||||
|
||||
var pos = node.pos || {x: 0, y: 0}
|
||||
var px = pos.x != null ? pos.x : (pos[0] || 0)
|
||||
var py = pos.y != null ? pos.y : (pos[1] || 0)
|
||||
var px = abs_x
|
||||
var py = abs_y
|
||||
var w = node.width || 1
|
||||
var h = node.height || 1
|
||||
var ax = node.anchor_x || 0
|
||||
@@ -442,7 +471,8 @@ function collect_drawables(node, camera, parent_tint, parent_opacity) {
|
||||
anchor_y: 0,
|
||||
uv_rect: uv,
|
||||
color: tint,
|
||||
material: node.material
|
||||
material: node.material,
|
||||
scissor: current_scissor
|
||||
})
|
||||
}
|
||||
|
||||
@@ -536,7 +566,7 @@ function collect_drawables(node, camera, parent_tint, parent_opacity) {
|
||||
type: 'sprite',
|
||||
layer: node.layer || 0,
|
||||
world_y: py,
|
||||
pos: pos,
|
||||
pos: {x: abs_x, y: abs_y},
|
||||
image: node.image,
|
||||
texture: node.texture,
|
||||
width: w,
|
||||
@@ -550,40 +580,44 @@ function collect_drawables(node, camera, parent_tint, parent_opacity) {
|
||||
}
|
||||
|
||||
if (node.type == 'text') {
|
||||
var pos = node.pos || {x: 0, y: 0}
|
||||
drawables.push({
|
||||
type: 'text',
|
||||
layer: node.layer || 0,
|
||||
world_y: pos.y != null ? pos.y : (pos[1] || 0),
|
||||
pos: pos,
|
||||
world_y: abs_y,
|
||||
pos: {x: abs_x, y: abs_y},
|
||||
text: node.text,
|
||||
font: node.font,
|
||||
size: node.size,
|
||||
color: tint_to_color(world_tint, world_opacity)
|
||||
color: tint_to_color(world_tint, world_opacity),
|
||||
scissor: current_scissor
|
||||
})
|
||||
}
|
||||
|
||||
if (node.type == 'rect') {
|
||||
var pos = node.pos || {x: 0, y: 0}
|
||||
drawables.push({
|
||||
type: 'rect',
|
||||
layer: node.layer || 0,
|
||||
world_y: pos.y != null ? pos.y : (pos[1] || 0),
|
||||
pos: pos,
|
||||
world_y: abs_y,
|
||||
pos: {x: abs_x, y: abs_y},
|
||||
width: node.width || 1,
|
||||
height: node.height || 1,
|
||||
color: tint_to_color(world_tint, world_opacity)
|
||||
color: tint_to_color(world_tint, world_opacity),
|
||||
scissor: current_scissor
|
||||
})
|
||||
}
|
||||
|
||||
if (node.type == 'particles' || node.particles) {
|
||||
var particles = node.particles || []
|
||||
for (var p of particles) {
|
||||
// Particles usually relative to emitter (node) pos
|
||||
var px = p.pos ? p.pos.x : 0
|
||||
var py = p.pos ? p.pos.y : 0
|
||||
|
||||
drawables.push({
|
||||
type: 'sprite',
|
||||
layer: node.layer || 0,
|
||||
world_y: p.pos ? p.pos.y : 0,
|
||||
pos: p.pos || {x: 0, y: 0},
|
||||
world_y: abs_y + py, // Sort by Y
|
||||
pos: {x: abs_x + px, y: abs_y + py}, // Add parent/node pos to particle pos
|
||||
image: node.image,
|
||||
texture: node.texture,
|
||||
width: (node.width || 1) * (p.scale || 1),
|
||||
@@ -591,7 +625,8 @@ function collect_drawables(node, camera, parent_tint, parent_opacity) {
|
||||
anchor_x: 0.5,
|
||||
anchor_y: 0.5,
|
||||
color: p.color || tint_to_color(world_tint, world_opacity),
|
||||
material: node.material
|
||||
material: node.material,
|
||||
scissor: current_scissor
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -610,8 +645,10 @@ function collect_drawables(node, camera, parent_tint, parent_opacity) {
|
||||
var tile = tiles[x][y]
|
||||
if (!tile) continue
|
||||
|
||||
var world_x = (x + offset_x) * scale_x
|
||||
var world_y_pos = (y + offset_y) * scale_y
|
||||
// Tile coords are strictly grid based + offset.
|
||||
// We should add this node's position (abs_x, abs_y) to it
|
||||
var world_x = abs_x + (x + offset_x) * scale_x
|
||||
var world_y_pos = abs_y + (y + offset_y) * scale_y
|
||||
|
||||
drawables.push({
|
||||
type: 'sprite',
|
||||
@@ -624,8 +661,10 @@ function collect_drawables(node, camera, parent_tint, parent_opacity) {
|
||||
height: scale_y,
|
||||
anchor_x: 0,
|
||||
anchor_y: 0,
|
||||
anchor_y: 0,
|
||||
color: tint_to_color(world_tint, world_opacity),
|
||||
material: node.material
|
||||
material: node.material,
|
||||
scissor: current_scissor
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -634,7 +673,7 @@ function collect_drawables(node, camera, parent_tint, parent_opacity) {
|
||||
// Recurse children
|
||||
if (node.children) {
|
||||
for (var child of node.children) {
|
||||
var child_drawables = collect_drawables(child, camera, world_tint, world_opacity)
|
||||
var child_drawables = collect_drawables(child, camera, world_tint, world_opacity, current_scissor, current_pos)
|
||||
drawables = drawables.concat(child_drawables)
|
||||
}
|
||||
}
|
||||
@@ -663,17 +702,20 @@ function batch_drawables(drawables) {
|
||||
if (drawable.type == 'sprite') {
|
||||
var texture = drawable.texture || drawable.image
|
||||
var material = drawable.material || {blend: 'alpha', sampler: 'nearest'}
|
||||
|
||||
// Start new batch if texture/material changed
|
||||
var scissor = drawable.scissor
|
||||
|
||||
// Start new batch if texture/material/scissor changed
|
||||
if (!current_batch ||
|
||||
current_batch.type != 'sprite_batch' ||
|
||||
current_batch.texture != texture ||
|
||||
!rect_equal(current_batch.scissor, scissor) ||
|
||||
!materials_equal(current_batch.material, material)) {
|
||||
if (current_batch) batches.push(current_batch)
|
||||
current_batch = {
|
||||
type: 'sprite_batch',
|
||||
texture: texture,
|
||||
material: material,
|
||||
scissor: scissor,
|
||||
sprites: []
|
||||
}
|
||||
}
|
||||
@@ -685,7 +727,7 @@ function batch_drawables(drawables) {
|
||||
batches.push(current_batch)
|
||||
current_batch = null
|
||||
}
|
||||
batches.push({type: drawable.type, drawable: drawable})
|
||||
batches.push({type: drawable.type, drawable: drawable, scissor: drawable.scissor})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -694,6 +736,12 @@ function batch_drawables(drawables) {
|
||||
return batches
|
||||
}
|
||||
|
||||
function rect_equal(a, b) {
|
||||
if (!a && !b) return true
|
||||
if (!a || !b) return false
|
||||
return a.x == b.x && a.y == b.y && a.width == b.width && a.height == b.height
|
||||
}
|
||||
|
||||
function materials_equal(a, b) {
|
||||
if (!a || !b) return a == b
|
||||
return a.blend == b.blend && a.sampler == b.sampler && a.shader == b.shader
|
||||
|
||||
24
sdl_gpu.cm
24
sdl_gpu.cm
@@ -668,7 +668,7 @@ function _build_sprite_vertices(sprites, camera) {
|
||||
vertex_data.wf(x)
|
||||
vertex_data.wf(y)
|
||||
vertex_data.wf(u0)
|
||||
vertex_data.wf(v0)
|
||||
vertex_data.wf(v1) // Flip V
|
||||
vertex_data.wf(c.r)
|
||||
vertex_data.wf(c.g)
|
||||
vertex_data.wf(c.b)
|
||||
@@ -678,7 +678,7 @@ function _build_sprite_vertices(sprites, camera) {
|
||||
vertex_data.wf(x + w)
|
||||
vertex_data.wf(y)
|
||||
vertex_data.wf(u1)
|
||||
vertex_data.wf(v0)
|
||||
vertex_data.wf(v1) // Flip V
|
||||
vertex_data.wf(c.r)
|
||||
vertex_data.wf(c.g)
|
||||
vertex_data.wf(c.b)
|
||||
@@ -688,7 +688,7 @@ function _build_sprite_vertices(sprites, camera) {
|
||||
vertex_data.wf(x + w)
|
||||
vertex_data.wf(y + h)
|
||||
vertex_data.wf(u1)
|
||||
vertex_data.wf(v1)
|
||||
vertex_data.wf(v0) // Flip V
|
||||
vertex_data.wf(c.r)
|
||||
vertex_data.wf(c.g)
|
||||
vertex_data.wf(c.b)
|
||||
@@ -698,7 +698,7 @@ function _build_sprite_vertices(sprites, camera) {
|
||||
vertex_data.wf(x)
|
||||
vertex_data.wf(y + h)
|
||||
vertex_data.wf(u0)
|
||||
vertex_data.wf(v1)
|
||||
vertex_data.wf(v0) // Flip V
|
||||
vertex_data.wf(c.r)
|
||||
vertex_data.wf(c.g)
|
||||
vertex_data.wf(c.b)
|
||||
@@ -1364,8 +1364,8 @@ function _do_mask(cmd_buffer, cmd) {
|
||||
uniform_data.wf(0) // padding
|
||||
uniform_data.wf(0) // padding
|
||||
|
||||
// Render pass
|
||||
var pass = cmd_buffer.render_pass({
|
||||
// Render to output
|
||||
var mask_pass = cmd_buffer.render_pass({
|
||||
color_targets: [{
|
||||
texture: output.texture,
|
||||
load: "clear",
|
||||
@@ -1374,17 +1374,17 @@ function _do_mask(cmd_buffer, cmd) {
|
||||
}]
|
||||
})
|
||||
|
||||
pass.bind_pipeline(_pipelines.mask)
|
||||
pass.bind_vertex_buffers(0, [{buffer: vb, offset: 0}])
|
||||
pass.bind_index_buffer({buffer: ib, offset: 0}, 16)
|
||||
mask_pass.bind_pipeline(_pipelines.mask)
|
||||
mask_pass.bind_vertex_buffers(0, [{buffer: vb, offset: 0}])
|
||||
mask_pass.bind_index_buffer({buffer: ib, offset: 0}, 16)
|
||||
// Bind both content texture (slot 0) and mask texture (slot 1)
|
||||
pass.bind_fragment_samplers(0, [
|
||||
mask_pass.bind_fragment_samplers(0, [
|
||||
{texture: content.texture, sampler: _sampler_nearest},
|
||||
{texture: mask.texture, sampler: _sampler_nearest}
|
||||
])
|
||||
cmd_buffer.push_fragment_uniform_data(0, stone(uniform_data))
|
||||
pass.draw_indexed(6, 1, 0, 0, 0)
|
||||
pass.end()
|
||||
mask_pass.draw_indexed(6, 1, 0, 0, 0)
|
||||
mask_pass.end()
|
||||
}
|
||||
|
||||
function _do_shader_pass(cmd_buffer, cmd) {
|
||||
|
||||
Reference in New Issue
Block a user