Hi!
I'm creating a data-driven rendering engine which is loosely based on Bitsquid/Stingray slides.
Rendering pipelines are compiled from descriptions written in a JSON-like DSL.
Until now, everything was nice and clean, the system allows to quickly prototype new renderers without writing tons of C++ glue.
But things get messy when the rendering pipeline must be changed at run-time (e.g. the player wants to toggle a certain feature on/off).
For instance, in the pipeline description all render targets are fixed, but if the user enables SSAO, the ueber_postprocessing shader must now output to two render targets instead of one. Similarly, if FXAA is disabled, the postprocessing shader must write straight to the backbuffer. There's a bunch of lesser problems (e.g. changing the render target clear colors to white when I want to take screenshots for whitepapers).
Here's my actual rendering engine pipeline description (with ugly HACKs):
| Tiled deferred rendering pipeline description.
|
; dependencies
includes = [
'renderer_common' ; defines common render states
'deferred_common' ; defines common render targets and the main depth-stencil
]
color_targets = [
; R8G8B8 - world-space normal, A8 - specular intensity
{
name = 'GBufferTexture0'
depends_on = 'BackBuffer'
;ScaleX = 1.0
;ScaleY = 1.0
sizeX = { size = 1 relative = 1 }
sizeY = { size = 1 relative = 1 }
format = 'RGBA8'
auto_clear = 1
;Info = 'Normals & Specular intensity'
}
; R8G8B8 - albedo, A8 - specular power
{
name = 'GBufferTexture1'
depends_on = 'BackBuffer'
;ScaleX = 1.0
;ScaleY = 1.0
sizeX = { size = 1 relative = 1 }
sizeY = { size = 1 relative = 1 }
format = 'RGBA8'
auto_clear = 1
;Info = 'Diffuse & Specular power'
}
]
depth_targets = [
; CSM/PSSM
{
name = 'ShadowMap'
sizeX = { size = 2048 relative = 0 }
sizeY = { size = 2048 relative = 0 }
format = 'D32'
sample = 1
}
]
|
35. Layers Configuration:
Defines the draw order of the visible batches in a game world
Layers are processed in the order they are declared
Shader system points out which layer to render in
|
layers =
[
; HACK:
{
id = 'FrameStart'
render_targets = [
{ name = '$Default' clear = 1 }
]
depth_stencil = { name = 'MainDepthStencil' clear = 1 }
profiling_scope = 'Clear FrameBuffer'
}
; Directional light
{
id = 'ShadowMap0'
; depth-only rendering
depth_stencil = { name = 'ShadowMap' clear = 1 }
sort = 'FrontToBack'
profiling_scope = 'ShadowMap0'
}
{
id = 'ShadowMap1'
; depth-only rendering
depth_stencil = { name = 'ShadowMap' }
sort = 'FrontToBack'
profiling_scope = 'ShadowMap1'
}
{
id = 'ShadowMap2'
; depth-only rendering
depth_stencil = { name = 'ShadowMap' }
sort = 'FrontToBack'
profiling_scope = 'ShadowMap2'
}
{
id = 'ShadowMap3'
; depth-only rendering
depth_stencil = { name = 'ShadowMap' }
sort = 'FrontToBack'
profiling_scope = 'ShadowMap3'
}
; G-Buffer Stage: Render all solid objects to a very sparse G-Buffer
{
id = 'FillGBuffer'
; Clear Geometry buffer.
; Bind render targets and main depth-stencil surface.
render_targets = [
{ name = 'GBufferTexture0' clear = 1 } ; normals
{ name = 'GBufferTexture1' clear = 1 } ; albedo
]
depth_stencil = { name = 'MainDepthStencil' clear = 1 }
;render_state = 'Default'
;default_shader = ?
;filter = Solid/Opaque only
sort = 'FrontToBack' ; Draw opaque objects front to back
profiling_scope = 'Fill G-Buffer'
; Fill buffers with material properties of *opaque* primitives.
}
; G-buffer has been filled with data.
; HACK:
{
id = 'ClearLightingTarget'
render_targets = [
{ name = 'LightingTarget' clear = 1 }
]
}
; point lights -> 'LightingTarget'
{
id = 'TiledDeferred_CS'
; Bind null color buffer and depth-stencil.
; No need to clear, we write all pixels.
profiling_scope = 'TiledDeferred_CS'
}
; Render deferred local lights.
{
id = 'DeferredLocalLights'
render_targets = [
{ name = 'LightingTarget' }
]
depth_stencil = { name = 'MainDepthStencil' }
profiling_scope = 'Deferred Local Lights'
}
; Apply deferred directional lights.
{
id = 'DeferredGlobalLights'
render_targets = [
{ name = 'LightingTarget' }
]
; Cannot bind the main depth-stencil surface - we read from it.
profiling_scope = 'Deferred Global Lights'
}
; Forward stage
{
id = 'Unlit'
render_targets = [
{ name = 'LightingTarget' }
]
depth_stencil = { name = 'MainDepthStencil' }
profiling_scope = 'Unlit'
}
{
id = 'SkyLast'
render_targets = [
{ name = 'LightingTarget' }
]
depth_stencil = { name = 'MainDepthStencil' }
profiling_scope = 'Sky'
}
; Alpha-Blended (Order-Dependent) Transparency
{
id = 'Translucent'
render_targets = [
{ name = 'LightingTarget' }
]
depth_stencil = { name = 'MainDepthStencil' }
}
; Blended Order-Independent Transparency (OIT):
;
{
id = 'BlendedOIT'
render_targets = [
{ name = 'BlendedOIT_Accumulation' clear = 1 }
{ name = 'BlendedOIT_Revealage' clear = 1 }
]
; Keep the depth buffer that was rendered for opaque surfaces
; bound for depth testing when rendering transparent surfaces,
; but do not write to it.
depth_stencil = { name = 'MainDepthStencil' }
}
; Combine: LightingTarget -> FrameBuffer
; Blended OIT: 2D Compositing Pass
{
id = 'DeferredCompositeLit'
render_targets = [
;TEMP HACK:
; { name = '$Default' }
{ name = 'FXAA_Input' }
]
profiling_scope = 'Tonemap'
}
; thick wireframes should be antialiased
{
id = 'DebugWireframe'
render_targets = [
;TEMP HACK:
; { name = '$Default' }
{ name = 'FXAA_Input' }
]
depth_stencil = { name = 'MainDepthStencil' }
profiling_scope = 'DebugWireframe'
}
; Fast Approximate Anti-Aliasing (FXAA)
{
id = 'FXAA'
render_targets = [
{ name = '$Default' }
]
}
; Draw debug lines and GUI above everything else
{
id = 'DebugLines'
render_targets = [
{ name = '$Default' }
]
depth_stencil = { name = 'MainDepthStencil' }
profiling_scope = 'DebugLines'
}
{
id = 'DebugScreenQuads'
render_targets = [
{ name = '$Default' }
]
profiling_scope = 'DebugScreenQuads'
}
; draw GUI above everything else
{
id = 'GUI'
render_targets = [
{ name = '$Default' }
]
profiling_scope = 'ImGui'
}
]
1) What is the cleanest way to allow on-the-fly modification of the rendering pipeline in a data-driven graphics engine?
2) How should render targets be described in a flexible rendering pipeline? How to specify that a render target should be taken from a render target pool?