Render script: GPU copy/blit from render targets into textures (mips / array layers / 3D slices)
- Dominant language
- C++
- Stars
- 6.3k
- Forks
- 455
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 129
Description
### Summary
In Defold’s render script API, there’s currently no way to **GPU-copy (blit) the contents of a render target** into an **existing texture resource**, e.g.:
* into a specific **mip level** of a 2D texture
* into a specific **layer/page** of a **texture array**
* into a specific **slice (z-layer)** of a **3D texture**
Having a fast, hardware-backed copy/blit would significantly simplify a lot of modern rendering techniques and remove the need for “atlas packing” workarounds.
### Motivation / Why this matters
Many rendering workflows produce intermediate images in render targets, then need to store them in a **single texture object** in a structured way (mip chain, array layers, 3D volume slices). On most graphics APIs this is a standard operation (copy image / blit image / resolve).
Without this, the only option is to do extra full-screen passes to manually assemble results (e.g. packing multiple downsampled images into one big atlas), which is:
* architecturally awkward
* more error-prone
* often more expensive than a direct GPU copy
* makes certain pipelines much harder to implement cleanly
### Concrete use cases
#### 1) Pre-filtered environment map (roughness mip chain)
A typical PBR pipeline needs a **prefiltered environment map** where different mip levels represent different roughness/blur levels. A common approach is:
1. render env map into a series of progressively smaller render targets (or reuse one RT and downsample repeatedly)
2. **copy each result into the corresponding mip level** of a texture
In older DX-style pipelines this is trivial via surface/texture copy. In Defold it’s currently not possible directly.
#### 2) Screen Space Local Reflections (previous-frame pyramid)
For SSR/SSLR it’s very useful to keep the **previous fully rendered frame** and build a **mip pyramid** (downsampled + optionally blurred versions). Ideally:
* do ~6–8 downsample passes
* **copy each downsampled RT into mip levels of a single texture**
* sample that texture in SSR according to roughness
Right now, the workaround is something like:
* still do the downsample passes
* then do an extra “packing” pass to place all levels side-by-side in one big atlas texture
This works, but it’s a hack and complicates everything (UV logic, padding, filtering issues, wasted space, etc.).
#### 3) Writing into texture arrays / 3D textures
Dynamic techniques sometimes need to write 2D results into:
* a **texture array layer** (paged texture)
* a **3D texture slice** (volume accumulation, cascades, history buffers, etc.)
A render-target-to-texture copy/blit would enable this cleanly.
### Requested feature
Add a render-script accessible function to perform a **GPU copy/blit** from a render target (or texture) into a destination texture subresource.
Key requirements:
* Source: render target attachment (color) (optionally depth later, but color is already huge)
* Destination: 2D texture / texture array / 3D texture
* Addressing destination subresource:
* **mip level**
* **array layer**
* **3D slice**
* Ability to specify optional rectangles (copy region) and filtering mode for scaling blits (nearest/linear), if supported.
### Possible API shape (example)
Something along these lines (names just illustrative):
```lua
-- Copy or blit from a render target attachment into a texture subresource.
render.blit({
src_render_target = rt,
src_attachment = render.BUFFER_COLOR0, -- or attachment index
src_x = 0, src_y = 0, src_w = w, src_h = h,
dst_texture = tex,
dst_mip = mip,
dst_layer = layer, -- for texture arrays (optional)
dst_slice = slice, -- for 3D textures (optional)
dst_x = 0, dst_y = 0, dst_w = w2, dst_h = h2,
filter = render.FILTER_LINEAR, -- optional
})
```
Notes:
* If `src_w/h != dst_w/h`, the operation is a scaling blit (if supported).
* If formats are incompatible, the function could error or fall back to a shader blit internally (engine choice).
* Even a **restricted “copy only, same size, same format”** version would already be extremely useful.
### Expected backend behavior
* On modern backends (Vulkan/Metal/D3D via bgfx), this maps well to native copy/blit operations.
* On OpenGL it maps to framebuffer blit / texture copy variants.
* On WebGL there are limitations, but even partial support (or a defined fallback path) would help; it’s fine if some platforms can’t support every variant.
### Workarounds today (and why they’re not great)
* “Atlas packing pass”: render multiple downsampled RTs into a single big texture by drawing quads.
* wastes space, adds complexity (UV remapping, padding, filtering artifacts)
* requires additional render passes and shader logic
* Keeping many separate textures alive:
* increases binding pressure / slot usage
* complicates sampling logic and resource management
### Why this is a good engine-level feature
This is one of those small primitives that unlocks many techniques:
* SSR/SSLR history pyramids
* environment prefiltering for PBR
* bloom/DOF pyramids stored in a single mipmapped texture
* temporal accumulation buffers
* writing into arrays/volumes for advanced effects
It makes these techniques **cleaner**, **faster**, and **more idiomatic** than forcing custom packing hacks.
Contributor guide
Assessment
This issue has not been assessed yet.