Working example
A working example on how to use the low level rendering API, using most of the functionality described in these manuals, can be found in the Framework/Examples/Source/LowLevelRendering project provided with the source code.
The example demonstrates how to render a textured cube mesh using the low-level rendering API. Below is a breakdown of the key steps involved.
Overview
The example performs the following operations:
- Creates GPU programs (vertex and fragment shaders)
- Creates a graphics pipeline state with blend and depth-stencil states
- Creates vertex and index buffers with appropriate vertex descriptions
- Creates textures and sampler states
- Sets up render targets
- Renders a textured cube every frame using command buffers
- Presents the result to the window
Setup phase
During setup, all necessary rendering resources are initialized on the render thread (see RenderThread for details on executing code on the render thread).
Creating GPU programs
GPU programs are created using GpuProgramCreateInformation and the GPU device:
const SPtr<GpuDevice> gpuDevice = GetApplication().GetPrimaryGpuDevice();
GpuProgramCreateInformation vertexProgramCreateInformation;
vertexProgramCreateInformation.Type = GPT_VERTEX_PROGRAM;
vertexProgramCreateInformation.EntryPoint = "main";
vertexProgramCreateInformation.Language = "hlsl"; // or "glsl" or "vksl"
vertexProgramCreateInformation.Source = vertexShaderSource;
SPtr<GpuProgram> vertexProgram = gpuDevice->CreateGpuProgram(vertexProgramCreateInformation);
GpuProgramCreateInformation fragmentProgramCreateInformation;
fragmentProgramCreateInformation.Type = GPT_FRAGMENT_PROGRAM;
fragmentProgramCreateInformation.EntryPoint = "main";
fragmentProgramCreateInformation.Language = "hlsl";
fragmentProgramCreateInformation.Source = fragmentShaderSource;
SPtr<GpuProgram> fragmentProgram = gpuDevice->CreateGpuProgram(fragmentProgramCreateInformation);
Creating pipeline state
A graphics pipeline state is created with blend and depth-stencil states:
BlendStateInformation blendStateInformation;
blendStateInformation.RenderTargets[0].BlendEnable = true;
blendStateInformation.RenderTargets[0].RenderTargetWriteMask = 0b0111; // RGB, don't write to alpha
blendStateInformation.RenderTargets[0].ColorBlendOperation = BO_ADD;
blendStateInformation.RenderTargets[0].ColorSourceFactor = BF_SOURCE_ALPHA;
blendStateInformation.RenderTargets[0].ColorDestinationFactor = BF_INV_SOURCE_ALPHA;
DepthStencilStateInformation depthStencilStateInformation;
depthStencilStateInformation.DepthWriteEnable = false;
depthStencilStateInformation.DepthReadEnable = false;
GpuGraphicsPipelineStateInformation pipelineStateInformation;
pipelineStateInformation.BlendState = blendStateInformation;
pipelineStateInformation.DepthStencilState = depthStencilStateInformation;
pipelineStateInformation.VertexProgram = vertexProgram;
pipelineStateInformation.FragmentProgram = fragmentProgram;
SPtr<GpuGraphicsPipelineState> pipelineState = gpuDevice->CreateGpuGraphicsPipelineState(pipelineStateInformation);
Creating GPU parameter sets
GPU parameter sets are created from the pipeline state's parameter set layout (obtained via GetParameterLayout()->GetSet()) and will hold uniform buffers, textures, and samplers for a specific descriptor set:
SPtr<GpuParameterSet> parameterSet = gpuDevice->CreateGpuParameterSet(pipelineState->GetParameterLayout()->GetSet(0), 0);
Creating vertex description
A vertex description defines the layout of vertex data:
TInlineArray<VertexElement, 8> vertexElements;
vertexElements.Add(VertexElement(VET_FLOAT3, VES_POSITION));
vertexElements.Add(VertexElement(VET_FLOAT2, VES_TEXCOORD));
SPtr<VertexDescription> vertexDescription = B3DMakeShared<VertexDescription>(vertexElements);
Creating vertex and index buffers
Vertex and index buffers are created using GpuBufferCreateInformation:
u32 vertexStride = vertexDescription->GetVertexStride();
u32 numVertices = 24;
GpuBufferCreateInformation vertexBufferCreateInformation;
vertexBufferCreateInformation.Type = GpuBufferType::Vertex;
vertexBufferCreateInformation.Vertex.Count = numVertices;
vertexBufferCreateInformation.Vertex.ElementSize = vertexStride;
SPtr<GpuBuffer> vertexBuffer = gpuDevice->CreateGpuBuffer(vertexBufferCreateInformation);
// Write vertex data
GpuBufferUtility::Write(vertexBuffer, 0, vertexStride * numVertices, vertexData, GpuBufferWriteFlag::Discard);
// Create index buffer
u32 numIndices = 36;
GpuBufferCreateInformation indexBufferCreateInformation;
indexBufferCreateInformation.Type = GpuBufferType::Index;
indexBufferCreateInformation.Index.Count = numIndices;
indexBufferCreateInformation.Index.Type = IT_32BIT;
SPtr<GpuBuffer> indexBuffer = gpuDevice->CreateGpuBuffer(indexBufferCreateInformation);
// Write index data
GpuBufferUtility::Write(indexBuffer, 0, numIndices * sizeof(u32), indexData, GpuBufferWriteFlag::Discard);
Creating textures and samplers
A texture is created from pixel data, and a sampler state is created to control texture filtering:
SPtr<PixelData> pixelData = PixelData::Create(2, 2, 1, PF_RGBA8);
pixelData->SetColorAt(Color::kWhite, 0, 0);
pixelData->SetColorAt(Color::kBlack, 1, 0);
pixelData->SetColorAt(Color::kWhite, 1, 1);
pixelData->SetColorAt(Color::kBlack, 0, 1);
SPtr<Texture> surfaceTexture = gpuDevice->CreateTexture(pixelData);
SamplerStateCreateInformation samplerStateCreateInformation;
samplerStateCreateInformation.MinFilter = FO_POINT;
samplerStateCreateInformation.MagFilter = FO_POINT;
SPtr<SamplerState> surfaceSampler = gpuDevice->FindOrCreateSamplerState(samplerStateCreateInformation);
Creating render targets
Render targets are created with color and depth attachments:
TextureCreateInformation colorAttachmentInformation;
colorAttachmentInformation.Width = 1280;
colorAttachmentInformation.Height = 720;
colorAttachmentInformation.Format = PF_RGBA8;
colorAttachmentInformation.Usage = TextureUsageFlag::RenderTarget;
SPtr<Texture> colorAttachment = gpuDevice->CreateTexture(colorAttachmentInformation);
TextureCreateInformation depthAttachmentInformation;
depthAttachmentInformation.Width = 1280;
depthAttachmentInformation.Height = 720;
depthAttachmentInformation.Format = PF_D32;
depthAttachmentInformation.Usage = TextureUsageFlag::DepthStencil;
SPtr<Texture> depthAttachment = gpuDevice->CreateTexture(depthAttachmentInformation);
RenderTextureCreateInformation renderTextureInformation;
renderTextureInformation.ColorSurfaces[0].Texture = colorAttachment;
renderTextureInformation.DepthStencilSurface.Texture = depthAttachment;
SPtr<RenderTexture> renderTarget = RenderTexture::Create(renderTextureInformation);
Render phase
Every frame, the following rendering operations are performed using a command buffer.
Creating command buffer
Command buffers are obtained from the command buffer pool:
const SPtr<GpuDevice>& gpuDevice = GetApplication().GetPrimaryGpuDevice();
const SPtr<GpuCommandBufferPool>& commandBufferPool = RendererManager::Instance().GetActive()->GetCommandBufferPool();
SPtr<GpuCommandBuffer> commandBuffer = commandBufferPool->Create(GpuCommandBufferCreateInformation::Create("LowLevelRendering"));
Setting up uniform buffers
Uniform data is written to a GPU buffer and bound to the parameters:
struct UniformBlock
{
Matrix4 MatrixWorldViewProjection;
Color Tint;
};
UniformBlock uniformBlock;
uniformBlock.MatrixWorldViewProjection = CalculateWorldViewProjectionMatrix();
uniformBlock.Tint = Color(1.0f, 1.0f, 1.0f, 0.5f);
SPtr<GpuBuffer> uniformBuffer = gpuDevice->CreateGpuBuffer(GpuBufferCreateInformation::CreateUniform(sizeof(UniformBlock)));
{
GpuBufferMappedScope mappedScope = uniformBuffer->Map(GpuMapOption::Write);
memcpy(mappedScope.GetMappedMemory(), &uniformBlock, sizeof(uniformBlock));
}
parameterSet->SetUniformBuffer("Params", uniformBuffer);
Binding textures and samplers
Textures and samplers are bound to the GPU parameter set:
parameterSet->SetSampledTexture("gMainTexture", surfaceTexture);
parameterSet->SetSamplerState("gMainTexture", surfaceSampler);
Beginning render pass
A render pass is started with the render target and the target is cleared:
render::RenderPassCreateInformation renderPassInformation(renderTarget);
renderPassInformation.ClearMask = RT_COLOR_ALL | RT_DEPTH;
renderPassInformation.ClearColor = Color::kBlue;
renderPassInformation.Parameters.Add(parameterSet);
commandBuffer->BeginRenderPass(renderPassInformation);
Binding pipeline and geometry
The pipeline state, vertex buffers, index buffer, and vertex description are bound:
commandBuffer->SetGpuGraphicsPipelineState(pipelineState);
commandBuffer->SetVertexBuffers(0, &vertexBuffer, 1);
commandBuffer->SetIndexBuffer(indexBuffer);
commandBuffer->SetVertexDescription(vertexDescription);
commandBuffer->SetDrawOperation(DOT_TRIANGLE_LIST);
Binding GPU parameter sets
GPU parameter sets containing uniform buffers, textures, and samplers are bound:
commandBuffer->SetGpuParameterSet(parameterSet);
Drawing
An indexed draw call is issued:
commandBuffer->DrawIndexed(0, numIndices, 0, numVertices, 1, 0);
Ending render pass
The render pass is ended after all draw calls have been issued:
commandBuffer->EndRenderPass();
Blitting to the window
Since the rendering was done to an offscreen render target, the result must be copied (blitted) to the render window's back buffer before presenting. Use render::RendererUtility::Blit with a render::BlitInformation to copy the color attachment to the render window. The static helper render::BlitInformation::BlitColor creates a blit descriptor with commonly used settings (no blending, no filtering):
// Get the color attachment from the offscreen render target
SPtr<Texture> colorTexture = renderTarget->GetColorTexture(0);
// Blit the offscreen color attachment to the render window
SPtr<RenderWindow> renderWindow = ...;
GetRendererUtility().Blit(*commandBuffer, BlitInformation::BlitColor(colorTexture, renderWindow));
Submitting and presenting
Finally, the command buffer is submitted for GPU execution and the render window is presented:
// Submit the command buffer for GPU execution
gpuDevice->SubmitCommandBuffer(commandBuffer);
// Present the rendered result to the window
gpuDevice->PresentRenderWindow(renderWindow);