diff --git a/.gitignore b/.gitignore index 442d4fbb0d..4eb4f1974d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ build*/ # Runtime output files for testing Screenshot.* Capture.* +*.Results.png # Output files from individual tests (Testbed is handled separately) tests/Output diff --git a/examples/C99/CMakeLists.txt b/examples/C99/CMakeLists.txt index bf20cd66ef..57584d24fb 100644 --- a/examples/C99/CMakeLists.txt +++ b/examples/C99/CMakeLists.txt @@ -16,6 +16,7 @@ project(LLGL_ExamplesC99) find_source_files(FilesExampleBaseC99 C "${EXAMPLE_C99_PROJECTS_DIR}/ExampleBase") find_project_source_files( FilesExampleC99_HelloTriangle "${EXAMPLE_C99_PROJECTS_DIR}/HelloTriangle" ) +find_project_source_files( FilesExampleC99_Offscreen "${EXAMPLE_C99_PROJECTS_DIR}/Offscreen" ) find_project_source_files( FilesExampleC99_Texturing "${EXAMPLE_C99_PROJECTS_DIR}/Texturing" ) @@ -53,6 +54,7 @@ if(LLGL_BUILD_EXAMPLES AND LLGL_BUILD_WRAPPER_C99) # C99 wrapper examples add_llgl_example_project(Example_C99_HelloTriangle C "${FilesExampleC99_HelloTriangle}" "${EXAMPLE_C99_PROJECT_LIBS}") + add_llgl_example_project(Example_C99_Offscreen C "${FilesExampleC99_Offscreen}" "${EXAMPLE_C99_PROJECT_LIBS}") add_llgl_example_project(Example_C99_Texturing C "${FilesExampleC99_Texturing}" "${EXAMPLE_C99_PROJECT_LIBS}") endif() diff --git a/examples/C99/ExampleBase/ExampleBase.c b/examples/C99/ExampleBase/ExampleBase.c index 9737987229..26bb663c7d 100644 --- a/examples/C99/ExampleBase/ExampleBase.c +++ b/examples/C99/ExampleBase/ExampleBase.c @@ -100,15 +100,6 @@ static void mouse_motion_event(LLGLWindow sender, const LLGLOffset2D* motion) * Global functions */ -struct ExampleConfig -{ - const char* rendererModule; - uint32_t windowSize[2]; - uint32_t samples; - bool vsync; - bool debugger; -}; - static struct ExampleConfig g_Config = { .rendererModule = "OpenGL", @@ -116,6 +107,7 @@ static struct ExampleConfig g_Config = .samples = 8, .vsync = true, .debugger = false, + .noDepthStencil = false }; static void update_viewport() @@ -138,6 +130,33 @@ static float aspect_ratio() return (float)swapChainResolution.width / (float)swapChainResolution.height; } +void example_config(const ExampleConfig* config) +{ + if (config != NULL) + { + if (config->rendererModule != NULL) + g_Config.rendererModule = config->rendererModule; + if (config->windowSize[0] != 0) + g_Config.windowSize[0] = config->windowSize[0]; + if (config->windowSize[1] != 0) + g_Config.windowSize[1] = config->windowSize[1]; + g_Config.samples = config->samples; + g_Config.vsync = config->vsync; + g_Config.debugger = config->debugger; + g_Config.noDepthStencil = config->noDepthStencil; + } + else + { + g_Config.rendererModule = "OpenGL"; + g_Config.windowSize[0] = 800; + g_Config.windowSize[1] = 600; + g_Config.samples = 8; + g_Config.vsync = true; + g_Config.debugger = false; + g_Config.noDepthStencil = false; + } +} + int example_init(const wchar_t* title) { // Register standard output as log callback @@ -155,10 +174,10 @@ int example_init(const wchar_t* title) LLGLSwapChainDescriptor swapChainDesc = { .resolution = { g_Config.windowSize[0], g_Config.windowSize[1] }, - .colorBits = 32, // 32 bits for color information - .depthBits = 24, // 24 bits for depth comparison - .stencilBits = 8, // 8 bits for stencil patterns - .samples = g_Config.samples, // check if LLGL adapts sample count that is too high + .colorBits = 32, // 32 bits for color information + .depthBits = (g_Config.noDepthStencil ? 0 : 24), // 24 bits for depth comparison + .stencilBits = (g_Config.noDepthStencil ? 0 : 8), // 8 bits for stencil patterns + .samples = g_Config.samples, // check if LLGL adapts sample count that is too high }; g_swapChain = llglCreateSwapChain(&swapChainDesc); @@ -249,10 +268,38 @@ bool example_poll_events() void perspective_projection(float outProjection[4][4], float aspectRatio, float nearPlane, float farPlane, float fieldOfView) { const int rendererID = llglGetRendererID(); - if (rendererID == LLGL_RENDERERID_OPENGL || rendererID == LLGL_RENDERERID_VULKAN) - build_perspective_projection(outProjection, aspectRatio, nearPlane, farPlane, fieldOfView, /*isUnitCube:*/ true); - else - build_perspective_projection(outProjection, aspectRatio, nearPlane, farPlane, fieldOfView, /*isUnitCube:*/ false); + const bool isUnitCube = (rendererID == LLGL_RENDERERID_OPENGL || rendererID == LLGL_RENDERERID_VULKAN); + build_perspective_projection(outProjection, aspectRatio, nearPlane, farPlane, fieldOfView, isUnitCube); +} + +static void build_orthogonal_projection(float m[4][4], float width, float height, float nearPlane, float farPlane, bool isUnitCube) +{ + m[0][0] = 2.0f / width; + m[0][1] = 0.0f; + m[0][2] = 0.0f; + m[0][3] = 0.0f; + + m[1][0] = 0.0f; + m[1][1] = 2.0f / height; + m[1][2] = 0.0f; + m[1][3] = 0.0f; + + m[2][0] = 0.0f; + m[2][1] = 0.0f; + m[2][2] = (isUnitCube ? 2.0f/(farPlane - nearPlane) : 1.0f/(farPlane - nearPlane)); + m[3][2] = 0.0f; + + m[3][0] = 0.0f; + m[3][1] = 0.0f; + m[2][3] = (isUnitCube ? -(farPlane + nearPlane)/(farPlane - nearPlane) : -nearPlane/(farPlane - nearPlane)); + m[3][3] = 1.0f; +} + +void orthogonal_projection(float outProjection[4][4], float width, float height, float nearPlane, float farPlane) +{ + const int rendererID = llglGetRendererID(); + const bool isUnitCube = (rendererID == LLGL_RENDERERID_OPENGL || rendererID == LLGL_RENDERERID_VULKAN); + build_orthogonal_projection(outProjection, width, height, nearPlane, farPlane, isUnitCube); } void get_textured_cube(const TexturedVertex** outVertices, size_t* outVertexCount, const uint32_t** outIndices, size_t* outIndexCount) diff --git a/examples/C99/ExampleBase/ExampleBase.h b/examples/C99/ExampleBase/ExampleBase.h index 58444b5c64..0f3fa9031b 100644 --- a/examples/C99/ExampleBase/ExampleBase.h +++ b/examples/C99/ExampleBase/ExampleBase.h @@ -26,6 +26,17 @@ * Structures */ +typedef struct ExampleConfig +{ + const char* rendererModule; + uint32_t windowSize[2]; + uint32_t samples; + bool vsync; + bool debugger; + bool noDepthStencil; +} +ExampleConfig; + typedef struct TexturedVertex { float position[3]; @@ -91,6 +102,9 @@ extern float g_projection[4][4]; * Global functions */ +// Configures the example setup. If used, it must be called before example_init(). If this is NULL, the defualt configuration will be used. +void example_config(const ExampleConfig* config); + // Initializes the example with the specified title and returns a non-zero error code if initialization failed. int example_init(const wchar_t* title); @@ -103,6 +117,9 @@ bool example_poll_events(); // Builds a perspective projection matrix. void perspective_projection(float outProjection[4][4], float aspectRatio, float nearPlane, float farPlane, float fieldOfView); +// Builds an orthogonal projection matrix. +void orthogonal_projection(float outProjection[4][4], float width, float height, float nearPlane, float farPlane); + // Returns the pointers the vertex and index data of a textured cube. void get_textured_cube(const TexturedVertex** outVertices, size_t* outVertexCount, const uint32_t** outIndices, size_t* outIndexCount); diff --git a/examples/C99/Offscreen/Offscreen.c b/examples/C99/Offscreen/Offscreen.c new file mode 100644 index 0000000000..afaaf052cb --- /dev/null +++ b/examples/C99/Offscreen/Offscreen.c @@ -0,0 +1,330 @@ +/* + * Offscreen.c + * + * Copyright (c) 2015 Lukas Hermanns. All rights reserved. + * Licensed under the terms of the BSD 3-Clause license (see LICENSE.txt). + */ + +/* +This example demonstates how to render offscreen, i.e. into a render-target without showing anything on the screen. +The rendered result will be written to a PNG file called "Offscreen.Results.png" in the same directory as this source file. +This image should look identical to the "Offscreen.png" image. +*/ + +#include +#include // printf() +#include // malloc()/free() +#include // sinf()/cosf() + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "../../../external/stb/stb_image_write.h" // stbi_write_png() + +#define FRAME_WIDTH 512 +#define FRAME_HEIGHT 512 +#define ENABLE_MULTISAMPLING 1 + +#ifndef M_PI +#define M_PI 3.141592654f +#endif + + +static const float g_colorWheel[6][3] = +{ + { 1.0f, 0.0f, 0.0f }, + { 1.0f, 1.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f }, + { 0.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 1.0f }, + { 1.0f, 0.0f, 1.0f }, +}; + +// Linear interpolation between a and b +float Lerp(float a, float b, float t) +{ + return a*(1.0f - t) + b*t; +} + +float LerpColorWheel(float t, int component) +{ + const int colorIndex = (int)(t*6.0f); + const float colorIndexRemainder = t*6.0f - (float)colorIndex; + return Lerp( + g_colorWheel[(colorIndex )%6][component], + g_colorWheel[(colorIndex + 1)%6][component], + colorIndexRemainder + ); +} + +int main(int argc, char* argv[]) +{ + // Load render system module + const char* rendererModule = "OpenGL"; + if (llglLoadRenderSystem(rendererModule) == 0) + { + fprintf(stderr, "Failed to load render system: %s\n", rendererModule); + return 1; + } + + // Print information about the selected renderer + LLGLRendererInfo info = {}; + llglGetRendererInfo(&info); + printf( + "Renderer: %s\n" + "Device: %s\n" + "Vendor: %s\n" + "Shading Language: %s\n", + info.rendererName, info.deviceName, info.vendorName, info.shadingLanguageName + ); + + // Vertex data structure + typedef struct Vertex + { + float position[2]; + float color[3]; + } + Vertex; + + // Generate vertices for strip geometry + const uint32_t numSegments = 64; + const uint32_t numVertices = (numSegments + 1)*2; + const size_t vertexBufferSize = sizeof(Vertex)*numVertices; + + Vertex* vertices = (Vertex*)malloc(vertexBufferSize); + if (vertices == NULL) + { + fprintf(stderr, "Failed to allocate %zu bytes for vertex buffer\n", vertexBufferSize); + return 1; + } + + const float invFrameScaleX = 1.0f / (float)numSegments; + const float invFrameScaleY = 1.0f / (float)numSegments; + + const float ringOuterRadius = 0.8f; + const float ringInnerRadius = 0.5f; + + for (size_t i = 0; i <= numSegments; ++i) + { + float u = (float)i * invFrameScaleX; + + float angle = u*M_PI*2.0f; + + float x0 = sinf(angle)*ringOuterRadius; + float y0 = cosf(angle)*ringOuterRadius; + + float x1 = sinf(angle)*ringInnerRadius; + float y1 = cosf(angle)*ringInnerRadius; + + float r = LerpColorWheel(u, 0); + float g = LerpColorWheel(u, 1); + float b = LerpColorWheel(u, 2); + + // Left-top vertex + vertices[i*2 ].position[0] = x0; + vertices[i*2 ].position[1] = y0; + vertices[i*2 ].color[0] = r; + vertices[i*2 ].color[1] = g; + vertices[i*2 ].color[2] = b; + + // Left-bottom vertex + vertices[i*2 + 1].position[0] = x1; + vertices[i*2 + 1].position[1] = y1; + vertices[i*2 + 1].color[0] = r; + vertices[i*2 + 1].color[1] = g; + vertices[i*2 + 1].color[2] = b; + } + + // Vertex format with 2D floating-point vector for position and 4D byte vector for color + LLGLVertexAttribute vertexAttributes[2] = + { + { .name = "position", .format = LLGLFormatRG32Float, .location = 0, .offset = offsetof(Vertex, position), .stride = sizeof(Vertex) }, + { .name = "color", .format = LLGLFormatRGB32Float, .location = 1, .offset = offsetof(Vertex, color ), .stride = sizeof(Vertex) }, + }; + + // Create vertex buffer + LLGLBufferDescriptor vertexBufferDesc = + { + .debugName = "VertexBuffer", + .size = vertexBufferSize, // Size (in bytes) of the vertex buffer + .bindFlags = LLGLBindVertexBuffer, // Enables the buffer to be bound to a vertex buffer slot + .numVertexAttribs = 2, + .vertexAttribs = vertexAttributes, // Vertex format layout + }; + LLGLBuffer vertexBuffer = llglCreateBuffer(&vertexBufferDesc, vertices); + + // Free temporariy resources + free(vertices); + + // Create shaders + LLGLShaderDescriptor vertShaderDesc = + { + .debugName = "VertexShader", + .type = LLGLShaderTypeVertex, + .source = "Offscreen.vert", + .sourceType = LLGLShaderSourceTypeCodeFile, + .flags = LLGLShaderCompilePatchClippingOrigin + }; + LLGLShaderDescriptor fragShaderDesc = + { + .debugName = "FragmentShader", + .type = LLGLShaderTypeFragment, + .source = "Offscreen.frag", + .sourceType = LLGLShaderSourceTypeCodeFile + }; + + // Specify vertex attributes for vertex shader + vertShaderDesc.vertex.numInputAttribs = 2; + vertShaderDesc.vertex.inputAttribs = vertexAttributes; + + LLGLShader shaders[2] = + { + llglCreateShader(&vertShaderDesc), + llglCreateShader(&fragShaderDesc), + }; + + for (int i = 0; i < 2; ++i) + { + LLGLReport shaderReport = llglGetShaderReport(shaders[i]); + if (llglHasReportErrors(shaderReport)) + { + fprintf(stderr, "%s\n", llglGetReportText(shaderReport)); + return 1; + } + } + + // Create texture to render into and read results from + LLGLTextureDescriptor textureDesc = + { + .debugName = "Offscreen.Texture", + .type = LLGLTextureTypeTexture2D, + .format = LLGLFormatRGBA8UNorm, + .extent = { FRAME_WIDTH, FRAME_HEIGHT, 1}, + .bindFlags = LLGLBindColorAttachment | LLGLBindCopySrc, + .mipLevels = 1, + .miscFlags = LLGLMiscNoInitialData, + }; + LLGLTexture texture = llglCreateTexture(&textureDesc, NULL); + + // Create offscreen render target to render into + LLGLRenderTargetDescriptor renderTargetDesc = + { + .debugName = "Offscreen.RenderTarget", + .renderPass = NULL, + .resolution = { FRAME_WIDTH, FRAME_HEIGHT }, + #if ENABLE_MULTISAMPLING + .samples = 8, + .colorAttachments[0] = { .format = LLGLFormatRGBA8UNorm }, // Let LLGL create an internal multi-sample texture with RGBA8UNorm format + .resolveAttachments[0] = { .texture = texture }, // Resolve multi-sampled texture into our output texture + #else + .colorAttachments[0] = { .texture = texture }, // Render directly into our output texture + #endif + }; + LLGLRenderTarget renderTarget = llglCreateRenderTarget(&renderTargetDesc); + + // Create graphics pipeline + LLGLGraphicsPipelineDescriptor pipelineDesc = + { + .vertexShader = shaders[0], + .fragmentShader = shaders[1], + .renderPass = llglGetRenderTargetRenderPass(renderTarget), + .primitiveTopology = LLGLPrimitiveTopologyTriangleStrip, + #if ENABLE_MULTISAMPLING + .rasterizer.multiSampleEnabled = true, + #endif + .blend.targets[0].colorMask = LLGLColorMaskAll, + }; + LLGLPipelineState pipeline = llglCreateGraphicsPipelineState(&pipelineDesc); + + // Link shader program and check for errors + LLGLReport pipelineReport = llglGetPipelineStateReport(pipeline); + if (llglHasReportErrors(pipelineReport)) + { + fprintf(stderr, "%s\n", llglGetReportText(pipelineReport)); + return 1; + } + + // Create command buffer to submit subsequent graphics commands to the GPU + LLGLCommandBufferDescriptor cmdBufferDesc = + { + .flags = LLGLCommandBufferImmediateSubmit, + .numNativeBuffers = 2, + }; + LLGLCommandBuffer cmdBuffer = llglCreateCommandBuffer(&cmdBufferDesc); + + // Initialize frame constants + const LLGLViewport viewport = + { + .x = 0.0f, + .y = 0.0f, + .width = (float)FRAME_WIDTH, + .height = (float)FRAME_HEIGHT, + .minDepth = 0.0f, + .maxDepth = 1.0f + }; + + const LLGLClearValue clearColor = + { + .color = { 0.1f, 0.1f, 0.2f, 1.0f }, + }; + + // Render single frame into offscreen render target + llglBegin(cmdBuffer); + { + // Set viewport and scissor rectangle + llglSetViewport(&viewport); + + // Set vertex buffer + llglSetVertexBuffer(vertexBuffer); + + // Set the swap-chain as the initial render target + llglBeginRenderPass(renderTarget); + { + // Clear color buffer + llglClear(LLGLClearColor, &clearColor); + + // Set graphics pipeline + llglSetPipelineState(pipeline); + + // Draw triangle with 3 vertices + llglDraw(numVertices, 0); + } + llglEndRenderPass(); + } + llglEnd(); + + // Read results from entire texture we just rendered into + const size_t imageRowStride = sizeof(uint32_t)*FRAME_WIDTH; + const size_t imageSize = imageRowStride * FRAME_HEIGHT; + + void* imageData = malloc(imageSize); + + LLGLMutableImageView dstImageView = + { + .format = LLGLImageFormatRGBA, + .dataType = LLGLDataTypeUInt8, + .data = imageData, + .dataSize = imageSize, + }; + LLGLTextureRegion dstRegion = + { + .subresource = { .numMipLevels = 1, .numArrayLayers = 1 }, + .offset = { 0, 0, 0 }, + .extent = textureDesc.extent, + }; + llglReadTexture(texture, &dstRegion, &dstImageView); + + // Save results to disk + const char* outputFilename = "Offscreen.Results.png"; + if (stbi_write_png(outputFilename, FRAME_WIDTH, FRAME_HEIGHT, 4, imageData, (int)imageRowStride) == 0) + { + fprintf(stderr, "Failed to save image to disk: %s\n", outputFilename); + return 1; + } + + // Free temporary resources + free(imageData); + + // Clean up + llglUnloadRenderSystem(); + + return 0; +} diff --git a/examples/C99/Offscreen/Offscreen.frag b/examples/C99/Offscreen/Offscreen.frag new file mode 100644 index 0000000000..9627899e17 --- /dev/null +++ b/examples/C99/Offscreen/Offscreen.frag @@ -0,0 +1,11 @@ +// GLSL shader version 3.30 (for OpenGL 3.3) +#version 330 + +in vec4 vColor; + +out vec4 outColor; + +void main() +{ + outColor = vColor; +} diff --git a/examples/C99/Offscreen/Offscreen.png b/examples/C99/Offscreen/Offscreen.png new file mode 100644 index 0000000000..7d06be26a4 Binary files /dev/null and b/examples/C99/Offscreen/Offscreen.png differ diff --git a/examples/C99/Offscreen/Offscreen.vert b/examples/C99/Offscreen/Offscreen.vert new file mode 100644 index 0000000000..c695326b4f --- /dev/null +++ b/examples/C99/Offscreen/Offscreen.vert @@ -0,0 +1,13 @@ +// GLSL shader version 3.30 (for OpenGL 3.3) +#version 330 + +in vec2 position; +in vec3 color; + +out vec4 vColor; + +void main() +{ + gl_Position = vec4(position, 0, 1); + vColor = vec4(color, 1); +} diff --git a/examples/C99/README.md b/examples/C99/README.md index 6738c3c0b7..c11f59f910 100644 --- a/examples/C99/README.md +++ b/examples/C99/README.md @@ -18,3 +18,9 @@ Texturing example with loading an image from file (using STB lib), indexed-drawi

+### [Offscreen](Offscreen) + +Offscreen example renders into a texture and outputs the result onto disk instead of the screen. + +

+