arcane_core/scripting/target_ops.rs
1/// Render-to-texture ops: create off-screen render targets, draw into them,
2/// then use them as TextureIds in subsequent drawSprite calls.
3///
4/// ## API (TS-side)
5/// ```ts
6/// const rt = createRenderTarget(256, 256); // → RenderTargetId (also usable as TextureId)
7/// beginRenderTarget(rt);
8/// drawSprite(...); // renders into rt's texture, camera: (0,0) = top-left
9/// endRenderTarget();
10/// drawSprite({ textureId: rt, x: 0, y: 0, w: 256, h: 256 });
11/// ```
12///
13/// ## Design
14/// - `op_create_render_target` allocates an ID from the shared `next_texture_id` counter
15/// (avoiding any collision with regular textures) and queues GPU resource creation.
16/// - `op_begin_render_target` sets `active_target = Some(id)`. While active,
17/// `op_draw_sprite` and `op_submit_sprite_batch` route commands to the target's
18/// queue instead of the main bridge sprite list.
19/// - `op_end_render_target` clears `active_target`.
20/// - dev.rs drains `create_queue`, `target_sprite_queues`, and `destroy_queue`
21/// each frame before the main render pass.
22
23use std::cell::RefCell;
24use std::collections::HashMap;
25use std::rc::Rc;
26
27use deno_core::OpState;
28
29use crate::renderer::SpriteCommand;
30use crate::scripting::render_ops::RenderBridgeState;
31
32/// State for all live render targets and the currently active one.
33pub struct TargetState {
34 /// If Some, sprite commands route to this target instead of the main pass.
35 pub active_target: Option<u32>,
36 /// GPU resource creation requests, drained by dev.rs each frame.
37 pub create_queue: Vec<(u32, u32, u32)>, // (id, width, height)
38 /// GPU resource destroy requests, drained by dev.rs each frame.
39 pub destroy_queue: Vec<u32>,
40 /// Per-target sprite command queues, drained by dev.rs for off-screen rendering.
41 pub target_sprite_queues: HashMap<u32, Vec<SpriteCommand>>,
42}
43
44impl TargetState {
45 pub fn new() -> Self {
46 Self {
47 active_target: None,
48 create_queue: Vec::new(),
49 destroy_queue: Vec::new(),
50 target_sprite_queues: HashMap::new(),
51 }
52 }
53}
54
55/// Create an off-screen render target of the given pixel dimensions.
56/// Returns an ID that doubles as both a `RenderTargetId` and a `TextureId`.
57///
58/// The ID is allocated from the shared texture ID pool to guarantee no
59/// collision with regular textures or other render targets.
60#[deno_core::op2(fast)]
61fn op_create_render_target(state: &mut OpState, w: f64, h: f64) -> u32 {
62 // Allocate from shared texture ID counter to avoid any collision
63 let id = {
64 let bridge = state.borrow_mut::<Rc<RefCell<RenderBridgeState>>>();
65 let mut b = bridge.borrow_mut();
66 let id = b.next_texture_id;
67 b.next_texture_id += 1;
68 id
69 };
70 let ts = state.borrow_mut::<Rc<RefCell<TargetState>>>();
71 ts.borrow_mut()
72 .create_queue
73 .push((id, w as u32, h as u32));
74 id
75}
76
77/// Route subsequent drawSprite calls into this render target.
78/// Coordinate system inside the target: (0, 0) = top-left of target.
79#[deno_core::op2(fast)]
80fn op_begin_render_target(state: &mut OpState, id: u32) {
81 let ts = state.borrow_mut::<Rc<RefCell<TargetState>>>();
82 ts.borrow_mut().active_target = Some(id);
83}
84
85/// Return to rendering into the main surface.
86#[deno_core::op2(fast)]
87fn op_end_render_target(state: &mut OpState) {
88 let ts = state.borrow_mut::<Rc<RefCell<TargetState>>>();
89 ts.borrow_mut().active_target = None;
90}
91
92/// Free the GPU resources for a render target.
93/// After this call, using the ID as a TextureId produces a transparent sprite.
94#[deno_core::op2(fast)]
95fn op_destroy_render_target(state: &mut OpState, id: u32) {
96 let ts = state.borrow_mut::<Rc<RefCell<TargetState>>>();
97 let mut ts = ts.borrow_mut();
98 ts.destroy_queue.push(id);
99 ts.target_sprite_queues.remove(&id);
100 // If this target was active, end it
101 if ts.active_target == Some(id) {
102 ts.active_target = None;
103 }
104}
105
106deno_core::extension!(
107 target_ext,
108 ops = [
109 op_create_render_target,
110 op_begin_render_target,
111 op_end_render_target,
112 op_destroy_render_target,
113 ],
114);