book/06-command-buffer-and-context.md

06. CommandBuffer와 ScriptableRenderContext

Summarizes the CommandBuffer recording/execution model, state leak prevention, relationship with RenderGraph, and profiling basics.

06. CommandBuffer and ScriptableRenderContext structure

This chapter covers “How to configure GPU instructions”. Even if you use RenderGraph, CommandBuffer is the execution unit internally, and it is handled directly in compatibility mode (Execute path).

6.1 What is CommandBuffer?

CommandBuffer is a list of rendering commands.

  • Draw call (DrawRenderer/DrawMesh)
  • Set render target (SetRenderTarget)
  • State settings (SetGlobal*, SetKeyword)
  • Bullet/Full Screen Pass

Rather than executing the same commands “immediately,” they are stacked and then submitted by the SRP at the appropriate timing.

6.1.1 Divide CommandBuffer into “State + Draw + Copy”

In practice, CommandBuffer is a mixture of the three types of behavior below.

  1. Status Settings
    • Corresponds to global constants/keywords/render states (blend/depth/stencil)
  2. Draw Call
    • Actual drawing such as DrawRenderers/DrawMesh/RendererList
  3. Copy/Full Screen
    • Blit, full screen triangle (post), texture conversion/downsample, etc.

When problems arise, there are often “state leaks”. It is important to be conscious of limiting the state on a pass-by-pass basis.

6.2 CommandBufferPool

In practice, doing new CommandBuffer() every frame creates a GC/allocation burden. So Unity provides a pool.

C#
var cmd = CommandBufferPool.Get("MyPass");
try
{
    // cmd 기록
    context.ExecuteCommandBuffer(cmd);
}
finally
{
    CommandBufferPool.Release(cmd);
}

6.3 (Important) Even in RenderGraph, cmd is ultimately the execution unit.

Even in the RenderGraph path, ctx.cmd of builder.SetRenderFunc((..., ctx) => { ... }) is a CommandBuffer.

That is:

  • RenderGraph: High-level structure that declares “dependencies/lifetime/order”
  • CommandBuffer: Sub-execution unit of “the actual command submitted to the GPU”

Debugging is possible only if you understand the two together.

6.3 Role of ScriptableRenderContext

ScriptableRenderContext is:

  • Perform culling
  • CommandBuffer execution/submission
  • Rendering state management

Responsible for etc. SRP runs its rendering pipeline through this context.

6.4 Common mistakes

  • CommandBuffer was logged but ExecuteCommandBuffer was not called.
  • Setting global state (keywords/global textures) and not releasing them → leaks to other cameras/passes
  • Redundant handling of RTHandle/RenderTexture lifetime management outside of the pass.

6.5 Render target setting (Load/Store) and tile GPU sense

On mobile/tile-based GPUs, “load/store” (memory round trip) significantly impacts performance.

Conceptually, render target binding involves:

  • LoadAction: Whether to read the previous content (Load) / ignore (DontCare/Clear)
  • StoreAction: Whether to store the result in memory (Store) / discard (DontCare)

The same concept exists, and URP tries to optimize it as much as possible under the hood.

RenderGraph also enforces “read/write declarations” to help with this optimization. (Related: 04. RenderGraph)

6.6 Profiling/Debugging: Make ProfilingScope a habit

As the number of passes increases, you can no longer see “where you are slow.” Unity/URP provides a profiling sampler.

C#
using (new ProfilingScope(cmd, new ProfilingSampler("MyPass")))
{
    // cmd 기록
}

Looking at RenderGraph Viewer + Profiler together:

  • Has the number of passes increased?
  • Is Blit/Copy excessive?
  • Is a particular pass taking longer than expected?

A routine to check is recommended.

Further reading (official/authoritative sources)