├── .clang-format ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── fixtures ├── airplane.jpg ├── bay_bridge.jpg ├── boston.jpg ├── demo │ ├── demo1.png │ ├── demo2.png │ ├── demo3.png │ ├── demo4.png │ ├── demo5.png │ └── demo6.png ├── embarcadero.jpg ├── helmet │ ├── Base.png │ ├── Emissive.png │ ├── Helmet.mtl │ ├── Helmet.obj │ └── Shadows.png ├── kalimba.jpg ├── marble.jpg ├── teapot │ ├── default.mtl │ ├── default.png │ └── teapot.obj └── tile.png ├── sft.sublime-project ├── src ├── CMakeLists.txt ├── canvas │ ├── CMakeLists.txt │ ├── canvas.cc │ ├── canvas.h │ ├── color_shader.cc │ ├── color_shader.h │ ├── paint.cc │ ├── paint.h │ ├── texture_shader.cc │ └── texture_shader.h ├── core │ ├── CMakeLists.txt │ ├── buffer.cc │ ├── buffer.h │ ├── buffer_view.cc │ ├── buffer_view.h │ ├── macros.h │ ├── mapping.cc │ ├── mapping.h │ ├── timing.cc │ └── timing.h ├── geometry │ ├── CMakeLists.txt │ ├── color.cc │ ├── color.h │ ├── geometry.cc │ ├── geometry.h │ ├── rect.cc │ ├── rect.h │ ├── size.cc │ ├── size.h │ ├── types.cc │ └── types.h ├── model │ ├── CMakeLists.txt │ ├── model.cc │ ├── model.h │ ├── model_shader.cc │ └── model_shader.h ├── playground │ ├── CMakeLists.txt │ ├── benchmarks.cc │ ├── fixtures_location.h.in │ ├── imgui_impl_sft.cc │ ├── imgui_impl_sft.h │ ├── imgui_shader.cc │ ├── imgui_shader.h │ ├── playground.cc │ ├── playground.h │ ├── playground_test.cc │ ├── playground_test.h │ ├── sdl_utils.cc │ ├── sdl_utils.h │ └── unittests.cc └── rasterizer │ ├── CMakeLists.txt │ ├── attachment.cc │ ├── attachment.h │ ├── blend.cc │ ├── blend.h │ ├── image.cc │ ├── image.h │ ├── invocation.cc │ ├── invocation.h │ ├── pipeline.cc │ ├── pipeline.h │ ├── rasterizer.cc │ ├── rasterizer.h │ ├── rasterizer_metrics.cc │ ├── rasterizer_metrics.h │ ├── render_pass.cc │ ├── render_pass.h │ ├── sampler.cc │ ├── sampler.h │ ├── shader.cc │ ├── shader.h │ ├── stage_resources.cc │ ├── stage_resources.h │ ├── texture.cc │ ├── texture.h │ ├── tiler.cc │ ├── tiler.h │ ├── uniforms.cc │ ├── uniforms.h │ ├── vertex_descriptor.cc │ └── vertex_descriptor.h ├── third_party └── superliminal │ ├── CMakeLists.txt │ ├── README.TXT │ └── RTree.h └── tools └── cmake └── sft_library.cmake /.clang-format: -------------------------------------------------------------------------------- 1 | # Defines the Chromium style for automatic reformatting. 2 | # http://clang.llvm.org/docs/ClangFormatStyleOptions.html 3 | BasedOnStyle: Chromium 4 | Standard: c++17 5 | EmptyLineBeforeAccessModifier: Always 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .DS_Store 3 | *.sublime-workspace 4 | .cache/ 5 | *.orig 6 | *.perfetto-trace 7 | *.ini 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/sdl"] 2 | path = third_party/sdl 3 | url = https://github.com/libsdl-org/SDL.git 4 | [submodule "third_party/tinyobjloader"] 5 | path = third_party/tinyobjloader 6 | url = https://github.com/tinyobjloader/tinyobjloader.git 7 | [submodule "third_party/googlebenchmark"] 8 | path = third_party/googlebenchmark 9 | url = https://github.com/google/benchmark.git 10 | [submodule "third_party/googletest"] 11 | path = third_party/googletest 12 | url = https://github.com/google/googletest.git 13 | [submodule "third_party/glm"] 14 | path = third_party/glm 15 | url = https://github.com/g-truc/glm.git 16 | [submodule "third_party/stb"] 17 | path = third_party/stb 18 | url = https://github.com/nothings/stb.git 19 | [submodule "third_party/imgui"] 20 | path = third_party/imgui 21 | url = https://github.com/ocornut/imgui.git 22 | [submodule "third_party/marl"] 23 | path = third_party/marl 24 | url = https://github.com/google/marl.git 25 | [submodule "third_party/cmake_toolbox"] 26 | path = third_party/cmake_toolbox 27 | url = https://github.com/chinmaygarde/cmake_toolbox.git 28 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake") 4 | include(sft_library) 5 | 6 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/third_party/cmake_toolbox") 7 | include(CMakeToolboxInitialize) 8 | 9 | CMakeToolboxInitialize() 10 | 11 | SetCAndCXXStandard(11 20) 12 | 13 | EnableCCache() 14 | 15 | project(sft) 16 | 17 | enable_testing() 18 | include(GoogleTest) 19 | include(CTest) 20 | 21 | add_subdirectory(third_party/sdl EXCLUDE_FROM_ALL) 22 | add_subdirectory(third_party/tinyobjloader EXCLUDE_FROM_ALL) 23 | add_subdirectory(third_party/googletest EXCLUDE_FROM_ALL) 24 | set(GOOGLETEST_PATH "third_party/googletest" EXCLUDE_FROM_ALL) 25 | set(BENCHMARK_ENABLE_TESTING FALSE) 26 | add_subdirectory(third_party/googlebenchmark EXCLUDE_FROM_ALL) 27 | add_subdirectory(third_party/glm EXCLUDE_FROM_ALL) 28 | set(MARL_BUILD_TESTS FALSE) 29 | set(MARL_BUILD_BENCHMARKS FALSE) 30 | set(MARL_BUILD_EXAMPLES FALSE) 31 | add_subdirectory(third_party/marl EXCLUDE_FROM_ALL) 32 | add_subdirectory(third_party/superliminal EXCLUDE_FROM_ALL) 33 | 34 | add_subdirectory(src) 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Chinmay Garde 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # This project uses CMake and Git sub-modules. This Makefile is just in place 2 | # to make common tasks easier. 3 | 4 | .PHONY: clean build 5 | 6 | run: build 7 | @./build/src/playground/playground 8 | 9 | test: build 10 | @ctest --test-dir build -R $(TEST_FILTER) 11 | 12 | build: build/build.ninja 13 | @cmake --build build 14 | 15 | build/build.ninja: 16 | @mkdir -p build 17 | cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release 18 | 19 | clean: 20 | @rm -rf build 21 | 22 | sync: 23 | @git submodule update --init --recursive -j 8 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Software Renderer 2 | 3 | A software renderer modelled after graphics APIs like Metal and Vulkan. A programmable shader based graphics pipeline, depth & stencil buffers, multi-sampling, tile-based-deferred-rendering, multi-core operation, blending, custom ImGUI integration, etc.. 4 | 5 | | Depth Buffers | Stencil Buffers | 6 | :-------------------------:|:---------------------------: 7 | ![](fixtures/demo/demo1.png) | ![](fixtures/demo/demo5.png) | 8 | | ImGUI Integration | Instrumentation | 9 | ![](fixtures/demo/demo2.png) | ![](fixtures/demo/demo4.png) | 10 | | Texture Sampling | Blending | 11 | ![](fixtures/demo/demo3.png) | ![](fixtures/demo/demo6.png) | 12 | 13 | ## Dependencies 14 | 15 | This is a very simple CMake project. The Makefile at the project root has tasks to make development easier. The Makefile does assume you have `cmake` and `ninja` installed on your host. 16 | 17 | ## Build & Run 18 | 19 | * `make sync` ensures that the right sub-module dependencies are pulled in. 20 | * `make run` runs the demo application. 21 | -------------------------------------------------------------------------------- /fixtures/airplane.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinmaygarde/sft/21a9f483126d1f15bc02536fa43c071eb640018b/fixtures/airplane.jpg -------------------------------------------------------------------------------- /fixtures/bay_bridge.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinmaygarde/sft/21a9f483126d1f15bc02536fa43c071eb640018b/fixtures/bay_bridge.jpg -------------------------------------------------------------------------------- /fixtures/boston.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinmaygarde/sft/21a9f483126d1f15bc02536fa43c071eb640018b/fixtures/boston.jpg -------------------------------------------------------------------------------- /fixtures/demo/demo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinmaygarde/sft/21a9f483126d1f15bc02536fa43c071eb640018b/fixtures/demo/demo1.png -------------------------------------------------------------------------------- /fixtures/demo/demo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinmaygarde/sft/21a9f483126d1f15bc02536fa43c071eb640018b/fixtures/demo/demo2.png -------------------------------------------------------------------------------- /fixtures/demo/demo3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinmaygarde/sft/21a9f483126d1f15bc02536fa43c071eb640018b/fixtures/demo/demo3.png -------------------------------------------------------------------------------- /fixtures/demo/demo4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinmaygarde/sft/21a9f483126d1f15bc02536fa43c071eb640018b/fixtures/demo/demo4.png -------------------------------------------------------------------------------- /fixtures/demo/demo5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinmaygarde/sft/21a9f483126d1f15bc02536fa43c071eb640018b/fixtures/demo/demo5.png -------------------------------------------------------------------------------- /fixtures/demo/demo6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinmaygarde/sft/21a9f483126d1f15bc02536fa43c071eb640018b/fixtures/demo/demo6.png -------------------------------------------------------------------------------- /fixtures/embarcadero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinmaygarde/sft/21a9f483126d1f15bc02536fa43c071eb640018b/fixtures/embarcadero.jpg -------------------------------------------------------------------------------- /fixtures/helmet/Base.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinmaygarde/sft/21a9f483126d1f15bc02536fa43c071eb640018b/fixtures/helmet/Base.png -------------------------------------------------------------------------------- /fixtures/helmet/Emissive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinmaygarde/sft/21a9f483126d1f15bc02536fa43c071eb640018b/fixtures/helmet/Emissive.png -------------------------------------------------------------------------------- /fixtures/helmet/Helmet.mtl: -------------------------------------------------------------------------------- 1 | # Blender 3.3.0 MTL File: 'None' 2 | # www.blender.org 3 | 4 | newmtl Material_MR 5 | Ns 250.000000 6 | Ka 1.000000 1.000000 1.000000 7 | Kd 0.800000 0.800000 0.800000 8 | Ks 0.500000 0.500000 0.500000 9 | Ni 1.500000 10 | d 1.000000 11 | illum 2 12 | map_Ke Helmet.png 13 | -------------------------------------------------------------------------------- /fixtures/helmet/Shadows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinmaygarde/sft/21a9f483126d1f15bc02536fa43c071eb640018b/fixtures/helmet/Shadows.png -------------------------------------------------------------------------------- /fixtures/kalimba.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinmaygarde/sft/21a9f483126d1f15bc02536fa43c071eb640018b/fixtures/kalimba.jpg -------------------------------------------------------------------------------- /fixtures/marble.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinmaygarde/sft/21a9f483126d1f15bc02536fa43c071eb640018b/fixtures/marble.jpg -------------------------------------------------------------------------------- /fixtures/teapot/default.mtl: -------------------------------------------------------------------------------- 1 | # Default material file. Created by Morgan McGuire and released into 2 | # the Public Domain on July 16, 2011. 3 | # 4 | # http://graphics.cs.williams.edu/data 5 | 6 | newmtl default 7 | Ns 10.0000 8 | Ni 1.5000 9 | d 1.0000 10 | Tr 0 0 11 | Tf 1 1 1 12 | illum 2 13 | Ka 1 1 1 14 | Kd 1 1 1 15 | Ks 0.2 0.2 0.2 16 | Ke 0 0 0 17 | map_Kd default.png 18 | -------------------------------------------------------------------------------- /fixtures/teapot/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinmaygarde/sft/21a9f483126d1f15bc02536fa43c071eb640018b/fixtures/teapot/default.png -------------------------------------------------------------------------------- /fixtures/tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinmaygarde/sft/21a9f483126d1f15bc02536fa43c071eb640018b/fixtures/tile.png -------------------------------------------------------------------------------- /sft.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "build_systems": [ 3 | { 4 | "name": "SFT Build All", 5 | "shell_cmd": "make test TEST_FILTER=CanDrawHelmet", 6 | "working_dir": "${project_path}", 7 | "file_regex": "(.*):(\\d+):(\\d+)(.*)", 8 | "shell": true, 9 | } 10 | ], 11 | "folders": [ 12 | { 13 | "path": ".", 14 | "name": "SFT", 15 | } 16 | ], 17 | "settings": 18 | { 19 | "LSP": 20 | { 21 | "clangd": 22 | { 23 | "initializationOptions" : { 24 | "binary": "system", 25 | "clangd.compile-commands-dir": "build", 26 | "clangd.background-index": true, 27 | "clangd.header-insertion": "never", 28 | "clangd.clang-tidy": true, 29 | } 30 | }, 31 | }, 32 | }, 33 | "debugger_configurations": 34 | [ 35 | { 36 | "type": "lldb", 37 | "request": "launch", 38 | "name": "Debug SFT", 39 | "program": "${folder}/build/sft", 40 | "args": [], 41 | "cwd": "${folder}" 42 | }, 43 | ], 44 | } 45 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(canvas) 2 | add_subdirectory(core) 3 | add_subdirectory(geometry) 4 | add_subdirectory(model) 5 | add_subdirectory(playground) 6 | add_subdirectory(rasterizer) 7 | -------------------------------------------------------------------------------- /src/canvas/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | sft_library(canvas 2 | canvas.cc 3 | canvas.h 4 | color_shader.cc 5 | color_shader.h 6 | paint.cc 7 | paint.h 8 | texture_shader.cc 9 | texture_shader.h 10 | ) 11 | 12 | target_link_libraries(canvas 13 | PUBLIC 14 | rasterizer 15 | ) 16 | -------------------------------------------------------------------------------- /src/canvas/canvas.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "canvas.h" 7 | 8 | #include "invocation.h" 9 | 10 | namespace sft { 11 | 12 | class CanvasShader final : public Shader { 13 | public: 14 | struct VertexData { 15 | glm::vec2 position; 16 | glm::vec2 uv; 17 | }; 18 | 19 | struct Uniforms { 20 | glm::mat4 xformation; 21 | glm::vec4 color; 22 | }; 23 | 24 | struct Varyings { 25 | glm::vec2 uv; 26 | }; 27 | 28 | CanvasShader() = default; 29 | 30 | size_t GetVaryingsSize() const override { return sizeof(Varyings); } 31 | 32 | glm::vec4 ProcessVertex(const VertexInvocation& inv) const override { 33 | FORWARD(uv, uv); 34 | return UNIFORM(xformation) * glm::vec4(VTX(position), 0.0, 1.0); 35 | } 36 | 37 | glm::vec4 ProcessFragment(const FragmentInvocation& inv) const override { 38 | auto color = UNIFORM(color); 39 | if (image_) { 40 | color *= image_->Sample(VARYING_LOAD(uv)); 41 | } 42 | return color; 43 | } 44 | 45 | void SetImage(const Image* texture) { image_ = texture; } 46 | 47 | private: 48 | const Image* image_; 49 | 50 | SFT_DISALLOW_COPY_AND_ASSIGN(CanvasShader); 51 | }; 52 | 53 | class CanvasContext { 54 | public: 55 | CanvasContext() { 56 | shader_ = std::make_shared(); 57 | pipeline_->shader = shader_; 58 | pipeline_->vertex_descriptor.stride = sizeof(CanvasShader::VertexData); 59 | pipeline_->vertex_descriptor.offset = 60 | offsetof(CanvasShader::VertexData, position); 61 | pipeline_->vertex_descriptor.index_type = IndexType::kUInt16; 62 | pipeline_->vertex_descriptor.vertex_format = VertexFormat::kFloat2; 63 | pipeline_->cull_face = std::nullopt; 64 | } 65 | 66 | ~CanvasContext() {} 67 | 68 | const std::shared_ptr& GetPipeline() { return pipeline_; } 69 | 70 | void SetImage(const Image* texture) { shader_->SetImage(texture); } 71 | 72 | private: 73 | std::shared_ptr shader_; 74 | std::shared_ptr pipeline_ = std::make_shared(); 75 | 76 | SFT_DISALLOW_COPY_AND_ASSIGN(CanvasContext); 77 | }; 78 | 79 | std::shared_ptr CanvasContextCreate() { 80 | return std::make_unique(); 81 | } 82 | 83 | Canvas::Canvas(std::shared_ptr context) 84 | : context_(std::move(context)) {} 85 | 86 | void Canvas::DrawRect(Rasterizer& rasterizer, Rect rect, const Paint& paint) { 87 | using VD = CanvasShader::VertexData; 88 | using Uniforms = CanvasShader::Uniforms; 89 | auto buffer = Buffer::Create(); 90 | auto vertex_buffer = buffer->Emplace(std::vector{ 91 | VD{.position = rect.origin, .uv = {0, 0}}, 92 | VD{.position = rect.origin + glm::vec2{rect.size.width, 0.0}, 93 | .uv = {1, 0}}, 94 | VD{.position = rect.origin + glm::vec2{rect.size.width, rect.size.height}, 95 | .uv = {1, 1}}, 96 | VD{.position = rect.origin + glm::vec2{0, rect.size.height}, 97 | .uv = {0, 1}}, 98 | }); 99 | 100 | auto size = glm::vec2{rasterizer.GetSize()}; 101 | auto projection = glm::ortho(0.f, size.x, size.y, 0.f); 102 | auto uniform_buffer = buffer->Emplace( 103 | Uniforms{.xformation = projection * ctm_, .color = paint.color}); 104 | auto index_buffer = buffer->Emplace(std::vector{0, 1, 2, 2, 3, 0}); 105 | 106 | auto pipeline = context_->GetPipeline(); 107 | 108 | pipeline->color_desc = paint.color_desc.value_or(ColorAttachmentDescriptor{}); 109 | 110 | pipeline->depth_desc = paint.depth_desc.value_or(DepthAttachmentDescriptor{}); 111 | 112 | pipeline->stencil_desc = 113 | paint.stencil_desc.value_or(StencilAttachmentDescriptor{}); 114 | 115 | context_->SetImage(paint.image.get()); 116 | 117 | rasterizer.Draw(pipeline, // 118 | vertex_buffer, // 119 | index_buffer, // 120 | uniform_buffer, // 121 | 6, // 122 | paint.stencil_reference // 123 | ); 124 | 125 | context_->SetImage(nullptr); 126 | } 127 | 128 | void Canvas::Translate(glm::vec2 tx) { 129 | ctm_ = glm::translate(ctm_, glm::vec3{tx, 0.0}); 130 | } 131 | 132 | void Canvas::Concat(const glm::mat4& xform) { 133 | ctm_ *= xform; 134 | } 135 | 136 | } // namespace sft 137 | -------------------------------------------------------------------------------- /src/canvas/canvas.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include "attachment.h" 11 | #include "geometry.h" 12 | #include "image.h" 13 | #include "macros.h" 14 | #include "paint.h" 15 | #include "rasterizer.h" 16 | #include "shader.h" 17 | 18 | namespace sft { 19 | 20 | class CanvasContext; 21 | 22 | std::shared_ptr CanvasContextCreate(); 23 | 24 | class Canvas { 25 | public: 26 | Canvas(std::shared_ptr context); 27 | 28 | void DrawRect(Rasterizer& rasterizer, Rect rect, const Paint& paint); 29 | 30 | void Translate(glm::vec2 tx); 31 | 32 | void Concat(const glm::mat4& xform); 33 | 34 | private: 35 | std::shared_ptr context_; 36 | glm::mat4 ctm_ = glm::identity(); 37 | 38 | SFT_DISALLOW_COPY_AND_ASSIGN(Canvas); 39 | }; 40 | 41 | } // namespace sft 42 | -------------------------------------------------------------------------------- /src/canvas/color_shader.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "color_shader.h" 7 | -------------------------------------------------------------------------------- /src/canvas/color_shader.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "geometry.h" 9 | #include "invocation.h" 10 | #include "shader.h" 11 | 12 | namespace sft { 13 | 14 | class ColorShader final : public Shader { 15 | public: 16 | struct VertexData { 17 | glm::vec3 position; 18 | }; 19 | 20 | struct Uniforms { 21 | glm::vec4 color; 22 | }; 23 | 24 | struct Varyings {}; 25 | 26 | ColorShader() = default; 27 | 28 | size_t GetVaryingsSize() const override { return sizeof(Varyings); } 29 | 30 | glm::vec4 ProcessVertex(const VertexInvocation& inv) const override { 31 | return {VTX(position), 1.0}; 32 | } 33 | 34 | glm::vec4 ProcessFragment(const FragmentInvocation& inv) const override { 35 | return UNIFORM(color); 36 | } 37 | 38 | private: 39 | SFT_DISALLOW_COPY_AND_ASSIGN(ColorShader); 40 | }; 41 | 42 | } // namespace sft 43 | -------------------------------------------------------------------------------- /src/canvas/paint.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "paint.h" 7 | -------------------------------------------------------------------------------- /src/canvas/paint.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include "attachment.h" 11 | #include "image.h" 12 | 13 | namespace sft { 14 | 15 | struct Paint { 16 | glm::vec4 color = kColorWhite; 17 | std::optional color_desc; 18 | std::optional depth_desc; 19 | std::optional stencil_desc; 20 | std::shared_ptr image; 21 | uint32_t stencil_reference = 0; 22 | }; 23 | 24 | } // namespace sft 25 | -------------------------------------------------------------------------------- /src/canvas/texture_shader.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "texture_shader.h" 7 | -------------------------------------------------------------------------------- /src/canvas/texture_shader.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include "image.h" 11 | #include "invocation.h" 12 | #include "macros.h" 13 | #include "shader.h" 14 | 15 | namespace sft { 16 | 17 | class TextureShader final : public Shader { 18 | public: 19 | struct VertexData { 20 | glm::vec2 texture_coords; 21 | glm::vec3 position; 22 | }; 23 | 24 | struct Uniforms { 25 | ScalarF alpha; 26 | glm::vec2 offset; 27 | }; 28 | 29 | struct Varyings { 30 | glm::vec2 texture_coords; 31 | }; 32 | 33 | TextureShader() = default; 34 | 35 | size_t GetVaryingsSize() const override { return sizeof(Varyings); } 36 | 37 | glm::vec4 ProcessVertex(const VertexInvocation& inv) const override { 38 | FORWARD(texture_coords, texture_coords); 39 | auto position = VTX(position); 40 | auto offset = UNIFORM(offset); 41 | position.x += offset.x; 42 | position.y += offset.y; 43 | return {position, 1}; 44 | } 45 | 46 | glm::vec4 ProcessFragment(const FragmentInvocation& inv) const override { 47 | auto color = inv.LoadImage(0).Sample(VARYING_LOAD(texture_coords)); 48 | const auto alpha = glm::clamp(UNIFORM(alpha), 0.0f, 1.0f); 49 | color.a *= alpha; 50 | return color; 51 | } 52 | 53 | private: 54 | SFT_DISALLOW_COPY_AND_ASSIGN(TextureShader); 55 | }; 56 | 57 | } // namespace sft 58 | -------------------------------------------------------------------------------- /src/core/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | sft_library(core 2 | buffer.cc 3 | buffer.h 4 | buffer_view.cc 5 | buffer_view.h 6 | macros.h 7 | mapping.cc 8 | mapping.h 9 | timing.cc 10 | timing.h 11 | ) 12 | -------------------------------------------------------------------------------- /src/core/buffer.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "buffer.h" 7 | 8 | namespace sft { 9 | 10 | const uint8_t* Buffer::GetData() const { 11 | return buffer_.data(); 12 | } 13 | 14 | std::shared_ptr Buffer::AsShared() { 15 | return shared_from_this(); 16 | } 17 | 18 | size_t Buffer::GetLength() const { 19 | return buffer_.size(); 20 | } 21 | 22 | } // namespace sft 23 | -------------------------------------------------------------------------------- /src/core/buffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "buffer_view.h" 13 | #include "macros.h" 14 | 15 | namespace sft { 16 | 17 | class Buffer final : public std::enable_shared_from_this { 18 | public: 19 | static std::shared_ptr Create() { 20 | return std::shared_ptr(new Buffer()); 21 | } 22 | 23 | ~Buffer() = default; 24 | 25 | template >> 26 | BufferView Emplace(const T& object) { 27 | return Emplace(reinterpret_cast(&object), sizeof(T)); 28 | } 29 | 30 | template 31 | BufferView Emplace(const std::vector& items) { 32 | const auto old_length = buffer_.size(); 33 | for (const auto& item : items) { 34 | Emplace(item); 35 | } 36 | return BufferView(*this, old_length, GetLength() - old_length); 37 | } 38 | 39 | BufferView Emplace(const uint8_t* buffer, size_t size) { 40 | const auto old_length = buffer_.size(); 41 | buffer_.resize(old_length + size); 42 | memmove(buffer_.data() + old_length, buffer, size); 43 | return {*this, old_length, GetLength() - old_length}; 44 | } 45 | 46 | const uint8_t* GetData() const; 47 | 48 | size_t GetLength() const; 49 | 50 | std::shared_ptr AsShared(); 51 | 52 | private: 53 | std::vector buffer_; 54 | 55 | Buffer() = default; 56 | 57 | SFT_DISALLOW_COPY_AND_ASSIGN(Buffer); 58 | }; 59 | 60 | } // namespace sft 61 | -------------------------------------------------------------------------------- /src/core/buffer_view.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "buffer_view.h" 7 | 8 | #include "buffer.h" 9 | 10 | namespace sft { 11 | 12 | BufferView::BufferView(Buffer& buffer) 13 | : buffer_(buffer.AsShared()), offset_(0u), length_(buffer.GetLength()) {} 14 | 15 | BufferView::BufferView(Buffer& buffer, size_t offset, size_t length) 16 | : buffer_(buffer.AsShared()), offset_(offset), length_(length) {} 17 | 18 | const uint8_t* BufferView::GetData() const { 19 | if (!buffer_) { 20 | return nullptr; 21 | } 22 | return buffer_->GetData() + offset_; 23 | } 24 | 25 | } // namespace sft 26 | -------------------------------------------------------------------------------- /src/core/buffer_view.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include "macros.h" 11 | 12 | namespace sft { 13 | 14 | class Buffer; 15 | 16 | struct BufferView { 17 | BufferView() : offset_(0u), length_(0u) {} 18 | 19 | BufferView(Buffer& buffer); 20 | 21 | BufferView(Buffer& buffer, size_t offset, size_t length); 22 | 23 | const uint8_t* GetData() const; 24 | 25 | operator bool() const { return static_cast(buffer_); } 26 | 27 | private: 28 | std::shared_ptr buffer_; 29 | size_t offset_; 30 | size_t length_; 31 | }; 32 | 33 | } // namespace sft 34 | -------------------------------------------------------------------------------- /src/core/macros.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #ifdef _MSC_VER 11 | #define SFT_COMPILER_MSVC 1 12 | #else // _MSC_VER 13 | #define SFT_COMPILER_CLANG 1 14 | #endif // _MSC_VER 15 | 16 | #define SFT_ASSERT(x) \ 17 | { \ 18 | if (!(x)) { \ 19 | std::cout << "SFT: Assertion " #x " failed." << std::endl; \ 20 | std::abort(); \ 21 | } \ 22 | } 23 | 24 | #define SFT_DISALLOW_COPY(TypeName) TypeName(const TypeName&) = delete 25 | 26 | #define SFT_DISALLOW_ASSIGN(TypeName) \ 27 | TypeName& operator=(const TypeName&) = delete 28 | 29 | #define SFT_DISALLOW_MOVE(TypeName) \ 30 | TypeName(TypeName&&) = delete; \ 31 | TypeName& operator=(TypeName&&) = delete 32 | 33 | #define SFT_DISALLOW_COPY_AND_ASSIGN(TypeName) \ 34 | TypeName(const TypeName&) = delete; \ 35 | TypeName& operator=(const TypeName&) = delete 36 | 37 | #define SFT_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName) \ 38 | TypeName(const TypeName&) = delete; \ 39 | TypeName(TypeName&&) = delete; \ 40 | TypeName& operator=(const TypeName&) = delete; \ 41 | TypeName& operator=(TypeName&&) = delete 42 | 43 | #define SFT_DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ 44 | TypeName() = delete; \ 45 | SFT_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName) 46 | 47 | #if SFT_COMPILER_MSVC 48 | #define SFT_ALWAYS_INLINE __inline __forceinline 49 | #else // SFT_COMPILER_MSVC 50 | #define SFT_ALWAYS_INLINE inline __attribute__((always_inline)) 51 | #endif // SFT_COMPILER_MSVC 52 | -------------------------------------------------------------------------------- /src/core/mapping.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "mapping.h" 7 | 8 | #include 9 | 10 | namespace sft { 11 | 12 | std::unique_ptr Mapping::MakeWithCopy(const uint8_t* buffer, 13 | size_t size) { 14 | auto copied = ::malloc(size); 15 | if (!copied) { 16 | return nullptr; 17 | } 18 | std::memmove(copied, buffer, size); 19 | return std::make_unique(reinterpret_cast(copied), 20 | size, [copied]() { ::free(copied); }); 21 | } 22 | 23 | Mapping::Mapping(const uint8_t* buffer, 24 | size_t size, 25 | std::function on_done) 26 | : buffer_(buffer), size_(size), on_done_(std::move(on_done)) {} 27 | 28 | Mapping::~Mapping() { 29 | if (on_done_) { 30 | on_done_(); 31 | } 32 | } 33 | 34 | const uint8_t* Mapping::GetBuffer() const { 35 | return buffer_; 36 | } 37 | 38 | size_t Mapping::GetSize() const { 39 | return size_; 40 | } 41 | 42 | } // namespace sft 43 | -------------------------------------------------------------------------------- /src/core/mapping.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | 11 | #include "macros.h" 12 | 13 | namespace sft { 14 | 15 | class Mapping { 16 | public: 17 | static std::unique_ptr MakeWithCopy(const uint8_t* buffer, 18 | size_t size); 19 | 20 | Mapping(const uint8_t* buffer, 21 | size_t size, 22 | std::function on_done = nullptr); 23 | 24 | ~Mapping(); 25 | 26 | const uint8_t* GetBuffer() const; 27 | 28 | size_t GetSize() const; 29 | 30 | private: 31 | const uint8_t* buffer_; 32 | size_t size_; 33 | std::function on_done_; 34 | 35 | SFT_DISALLOW_COPY_AND_ASSIGN(Mapping); 36 | }; 37 | 38 | } // namespace sft 39 | -------------------------------------------------------------------------------- /src/core/timing.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "timing.h" 7 | -------------------------------------------------------------------------------- /src/core/timing.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | namespace sft { 11 | 12 | using MillisecondsF = std::chrono::duration; 13 | using SecondsF = std::chrono::duration; 14 | using Clock = std::chrono::high_resolution_clock; 15 | using TimePoint = std::chrono::time_point; 16 | 17 | } // namespace sft 18 | -------------------------------------------------------------------------------- /src/geometry/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | sft_library(geometry 2 | color.cc 3 | color.h 4 | geometry.cc 5 | geometry.h 6 | rect.cc 7 | rect.h 8 | size.cc 9 | size.h 10 | types.cc 11 | types.h 12 | ) 13 | 14 | target_link_libraries(geometry 15 | PUBLIC 16 | glm 17 | ) 18 | -------------------------------------------------------------------------------- /src/geometry/color.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "color.h" 7 | 8 | namespace sft {} 9 | -------------------------------------------------------------------------------- /src/geometry/color.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include "types.h" 11 | 12 | namespace sft { 13 | 14 | struct ColorF { 15 | ScalarF red = 0.0; 16 | ScalarF green = 0.0; 17 | ScalarF blue = 0.0; 18 | ScalarF alpha = 0.0; 19 | 20 | constexpr ColorF() = default; 21 | 22 | constexpr ColorF(ScalarF p_red, 23 | ScalarF p_green, 24 | ScalarF p_blue, 25 | ScalarF p_alpha) 26 | : red(p_red), green(p_green), blue(p_blue), alpha(p_alpha) {} 27 | 28 | constexpr ColorF(glm::vec3 color, ScalarF p_alpha) 29 | : red(color.r), green(color.g), blue(color.b), alpha(p_alpha) {} 30 | 31 | constexpr glm::vec3 GetColor() const { return {red, green, blue}; } 32 | 33 | constexpr ScalarF GetAlpha() const { return alpha; }; 34 | 35 | constexpr ColorF Premultiply() const { 36 | return {red * alpha, green * alpha, blue * alpha, alpha}; 37 | } 38 | }; 39 | 40 | struct Color { 41 | union { 42 | uint32_t color; 43 | struct { 44 | uint8_t red; 45 | uint8_t green; 46 | uint8_t blue; 47 | uint8_t alpha; 48 | }; 49 | }; 50 | 51 | constexpr Color() : Color(0) {} 52 | 53 | constexpr Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) 54 | : red(r), green(g), blue(b), alpha(a) {} 55 | 56 | constexpr Color(const glm::vec4& c) 57 | : Color(255 * c.r, 255 * c.g, 255 * c.b, 255 * c.a) {} 58 | 59 | constexpr Color(const glm::vec3& c) 60 | : Color(255 * c.r, 255 * c.g, 255 * c.b, 255) {} 61 | 62 | constexpr Color(uint32_t p_color) : color(p_color) {} 63 | 64 | constexpr Color(ColorF c) 65 | : Color(255 * c.red, 255 * c.green, 255 * c.blue, 255 * c.alpha) {} 66 | 67 | constexpr operator uint32_t() const { return color; } 68 | 69 | constexpr operator glm::vec4() const { 70 | return { 71 | red / 255.0, // 72 | green / 255.0, // 73 | blue / 255.0, // 74 | alpha / 255.0 // 75 | }; 76 | } 77 | 78 | constexpr Color WithRed(uint8_t color) const { 79 | return Color(color, green, blue, alpha); 80 | } 81 | 82 | constexpr Color WithGreen(uint8_t color) const { 83 | return Color(red, color, blue, alpha); 84 | } 85 | 86 | constexpr Color WithBlue(uint8_t color) const { 87 | return Color(red, green, color, alpha); 88 | } 89 | 90 | constexpr Color WithAlpha(uint8_t color) const { 91 | return Color(red, green, blue, color); 92 | } 93 | 94 | constexpr ColorF GetColorF() const { 95 | return { 96 | red / 255.0f, // 97 | green / 255.0f, // 98 | blue / 255.0f, // 99 | alpha / 255.0f // 100 | }; 101 | } 102 | 103 | static constexpr Color FromComponentsF(ScalarF r, 104 | ScalarF g, 105 | ScalarF b, 106 | ScalarF a) { 107 | return Color{ 108 | static_cast(255 * r), 109 | static_cast(255 * g), 110 | static_cast(255 * b), 111 | static_cast(255 * a), 112 | }; 113 | } 114 | 115 | static Color Random() { 116 | return Color{static_cast(std::rand() % 255), 117 | static_cast(std::rand() % 255), 118 | static_cast(std::rand() % 255), 255}; 119 | } 120 | 121 | static constexpr Color Gray(ScalarF gray) { 122 | const uint8_t g = 255 * glm::clamp(gray, 0.0, 1.0); 123 | return Color{g, g, g, 255}; 124 | } 125 | }; 126 | 127 | constexpr Color kColorRed = {255, 0, 0, 255}; 128 | constexpr Color kColorGreen = {0, 255, 0, 255}; 129 | constexpr Color kColorBlue = {0, 0, 255, 255}; 130 | constexpr Color kColorWhite = {255, 255, 255, 255}; 131 | constexpr Color kColorBlack = {0, 0, 0, 255}; 132 | constexpr Color kColorTransparentBlack = {0, 0, 0, 0}; 133 | constexpr Color kColorOpaqueBlack = {0, 0, 0, 255}; 134 | constexpr Color kColorAliceBlue = {240, 248, 255, 255}; 135 | constexpr Color kColorAntiqueWhite = {250, 235, 215, 255}; 136 | constexpr Color kColorAqua = {0, 255, 255, 255}; 137 | constexpr Color kColorAquaMarine = {127, 255, 212, 255}; 138 | constexpr Color kColorAzure = {240, 255, 255, 255}; 139 | constexpr Color kColorBeige = {245, 245, 220, 255}; 140 | constexpr Color kColorBisque = {255, 228, 196, 255}; 141 | constexpr Color kColorBlanchedAlmond = {255, 235, 205, 255}; 142 | constexpr Color kColorBlueViolet = {138, 43, 226, 255}; 143 | constexpr Color kColorBrown = {165, 42, 42, 255}; 144 | constexpr Color kColorBurlyWood = {222, 184, 135, 255}; 145 | constexpr Color kColorCadetBlue = {95, 158, 160, 255}; 146 | constexpr Color kColorChartreuse = {127, 255, 0, 255}; 147 | constexpr Color kColorChocolate = {210, 105, 30, 255}; 148 | constexpr Color kColorCoral = {255, 127, 80, 255}; 149 | constexpr Color kColorCornflowerBlue = {100, 149, 237, 255}; 150 | constexpr Color kColorCornsilk = {255, 248, 220, 255}; 151 | constexpr Color kColorCrimson = {220, 20, 60, 255}; 152 | constexpr Color kColorCyan = {0, 255, 255, 255}; 153 | constexpr Color kColorDarkBlue = {0, 0, 139, 255}; 154 | constexpr Color kColorDarkCyan = {0, 139, 139, 255}; 155 | constexpr Color kColorDarkGoldenrod = {184, 134, 11, 255}; 156 | constexpr Color kColorDarkGray = {169, 169, 169, 255}; 157 | constexpr Color kColorDarkGreen = {0, 100, 0, 255}; 158 | constexpr Color kColorDarkGrey = {169, 169, 169, 255}; 159 | constexpr Color kColorDarkKhaki = {189, 183, 107, 255}; 160 | constexpr Color kColorDarkMagenta = {139, 0, 139, 255}; 161 | constexpr Color kColorDarkOliveGreen = {85, 107, 47, 255}; 162 | constexpr Color kColorDarkOrange = {255, 140, 0, 255}; 163 | constexpr Color kColorDarkOrchid = {153, 50, 204, 255}; 164 | constexpr Color kColorDarkRed = {139, 0, 0, 255}; 165 | constexpr Color kColorDarkSalmon = {233, 150, 122, 255}; 166 | constexpr Color kColorDarkSeagreen = {143, 188, 143, 255}; 167 | constexpr Color kColorDarkSlateBlue = {72, 61, 139, 255}; 168 | constexpr Color kColorDarkSlateGray = {47, 79, 79, 255}; 169 | constexpr Color kColorDarkSlateGrey = {47, 79, 79, 255}; 170 | constexpr Color kColorDarkTurquoise = {0, 206, 209, 255}; 171 | constexpr Color kColorDarkViolet = {148, 0, 211, 255}; 172 | constexpr Color kColorDeepPink = {255, 20, 147, 255}; 173 | constexpr Color kColorDeepSkyBlue = {0, 191, 255, 255}; 174 | constexpr Color kColorDimGray = {105, 105, 105, 255}; 175 | constexpr Color kColorDimGrey = {105, 105, 105, 255}; 176 | constexpr Color kColorDodgerBlue = {30, 144, 255, 255}; 177 | constexpr Color kColorFirebrick = {178, 34, 34, 255}; 178 | constexpr Color kColorFloralWhite = {255, 250, 240, 255}; 179 | constexpr Color kColorForestGreen = {34, 139, 34, 255}; 180 | constexpr Color kColorFuchsia = {255, 0, 255, 255}; 181 | constexpr Color kColorGainsboro = {220, 220, 220, 255}; 182 | constexpr Color kColorGhostwhite = {248, 248, 255, 255}; 183 | constexpr Color kColorGold = {255, 215, 0, 255}; 184 | constexpr Color kColorGoldenrod = {218, 165, 32, 255}; 185 | constexpr Color kColorGray = {128, 128, 128, 255}; 186 | constexpr Color kColorGreenYellow = {173, 255, 47, 255}; 187 | constexpr Color kColorGrey = {128, 128, 128, 255}; 188 | constexpr Color kColorHoneydew = {240, 255, 240, 255}; 189 | constexpr Color kColorHotPink = {255, 105, 180, 255}; 190 | constexpr Color kColorIndianRed = {205, 92, 92, 255}; 191 | constexpr Color kColorIndigo = {75, 0, 130, 255}; 192 | constexpr Color kColorIvory = {255, 255, 240, 255}; 193 | constexpr Color kColorKhaki = {240, 230, 140, 255}; 194 | constexpr Color kColorLavender = {230, 230, 250, 255}; 195 | constexpr Color kColorLavenderBlush = {255, 240, 245, 255}; 196 | constexpr Color kColorLawnGreen = {124, 252, 0, 255}; 197 | constexpr Color kColorLemonChiffon = {255, 250, 205, 255}; 198 | constexpr Color kColorLightBlue = {173, 216, 230, 255}; 199 | constexpr Color kColorLightCoral = {240, 128, 128, 255}; 200 | constexpr Color kColorLightCyan = {224, 255, 255, 255}; 201 | constexpr Color kColorLightGoldenrodYellow = {50, 250, 210, 255}; 202 | constexpr Color kColorLightGray = {211, 211, 211, 255}; 203 | constexpr Color kColorLightGreen = {144, 238, 144, 255}; 204 | constexpr Color kColorLightGrey = {211, 211, 211, 255}; 205 | constexpr Color kColorLightPink = {255, 182, 193, 255}; 206 | constexpr Color kColorLightSalmon = {255, 160, 122, 255}; 207 | constexpr Color kColorLightSeaGreen = {32, 178, 170, 255}; 208 | constexpr Color kColorLightSkyBlue = {135, 206, 250, 255}; 209 | constexpr Color kColorLightSlateGray = {119, 136, 153, 255}; 210 | constexpr Color kColorLightSlateGrey = {119, 136, 153, 255}; 211 | constexpr Color kColorLightSteelBlue = {176, 196, 222, 255}; 212 | constexpr Color kColorLightYellow = {255, 255, 224, 255}; 213 | constexpr Color kColorLime = {0, 255, 0, 255}; 214 | constexpr Color kColorLimeGreen = {50, 205, 50, 255}; 215 | constexpr Color kColorLinen = {250, 240, 230, 255}; 216 | constexpr Color kColorMagenta = {255, 0, 255, 255}; 217 | constexpr Color kColorMaroon = {128, 0, 0, 255}; 218 | constexpr Color kColorMediumAquamarine = {102, 205, 170, 255}; 219 | constexpr Color kColorMediumBlue = {0, 0, 205, 255}; 220 | constexpr Color kColorMediumOrchid = {186, 85, 211, 255}; 221 | constexpr Color kColorMediumPurple = {147, 112, 219, 255}; 222 | constexpr Color kColorMediumSeagreen = {60, 179, 113, 255}; 223 | constexpr Color kColorMediumSlateBlue = {123, 104, 238, 255}; 224 | constexpr Color kColorMediumSpringGreen = {0, 250, 154, 255}; 225 | constexpr Color kColorMediumTurquoise = {72, 209, 204, 255}; 226 | constexpr Color kColorMediumVioletRed = {199, 21, 133, 255}; 227 | constexpr Color kColorMidnightBlue = {25, 25, 112, 255}; 228 | constexpr Color kColorMintCream = {245, 255, 250, 255}; 229 | constexpr Color kColorMistyRose = {255, 228, 225, 255}; 230 | constexpr Color kColorMoccasin = {255, 228, 181, 255}; 231 | constexpr Color kColorNavajoWhite = {255, 222, 173, 255}; 232 | constexpr Color kColorNavy = {0, 0, 128, 255}; 233 | constexpr Color kColorOldLace = {253, 245, 230, 255}; 234 | constexpr Color kColorOlive = {128, 128, 0, 255}; 235 | constexpr Color kColorOliveDrab = {107, 142, 35, 255}; 236 | constexpr Color kColorOrange = {255, 165, 0, 255}; 237 | constexpr Color kColorOrangeRed = {255, 69, 0, 255}; 238 | constexpr Color kColorOrchid = {218, 112, 214, 255}; 239 | constexpr Color kColorPaleGoldenrod = {238, 232, 170, 255}; 240 | constexpr Color kColorPaleGreen = {152, 251, 152, 255}; 241 | constexpr Color kColorPaleTurquoise = {175, 238, 238, 255}; 242 | constexpr Color kColorPaleVioletRed = {219, 112, 147, 255}; 243 | constexpr Color kColorPapayaWhip = {255, 239, 213, 255}; 244 | constexpr Color kColorPeachpuff = {255, 218, 185, 255}; 245 | constexpr Color kColorPeru = {205, 133, 63, 255}; 246 | constexpr Color kColorPink = {255, 192, 203, 255}; 247 | constexpr Color kColorPlum = {221, 160, 221, 255}; 248 | constexpr Color kColorPowderBlue = {176, 224, 230, 255}; 249 | constexpr Color kColorPurple = {128, 0, 128, 255}; 250 | constexpr Color kColorRosyBrown = {188, 143, 143, 255}; 251 | constexpr Color kColorRoyalBlue = {65, 105, 225, 255}; 252 | constexpr Color kColorSaddleBrown = {139, 69, 19, 255}; 253 | constexpr Color kColorSalmon = {250, 128, 114, 255}; 254 | constexpr Color kColorSandyBrown = {244, 164, 96, 255}; 255 | constexpr Color kColorSeagreen = {46, 139, 87, 255}; 256 | constexpr Color kColorSeashell = {255, 245, 238, 255}; 257 | constexpr Color kColorSienna = {160, 82, 45, 255}; 258 | constexpr Color kColorSilver = {192, 192, 192, 255}; 259 | constexpr Color kColorSkyBlue = {135, 206, 235, 255}; 260 | constexpr Color kColorSlateBlue = {106, 90, 205, 255}; 261 | constexpr Color kColorSlateGray = {112, 128, 144, 255}; 262 | constexpr Color kColorSlateGrey = {112, 128, 144, 255}; 263 | constexpr Color kColorSnow = {255, 250, 250, 255}; 264 | constexpr Color kColorSpringGreen = {0, 255, 127, 255}; 265 | constexpr Color kColorSteelBlue = {70, 130, 180, 255}; 266 | constexpr Color kColorTan = {210, 180, 140, 255}; 267 | constexpr Color kColorTeal = {0, 128, 128, 255}; 268 | constexpr Color kColorThistle = {216, 191, 216, 255}; 269 | constexpr Color kColorTomato = {255, 99, 71, 255}; 270 | constexpr Color kColorTurquoise = {64, 224, 208, 255}; 271 | constexpr Color kColorViolet = {238, 130, 238, 255}; 272 | constexpr Color kColorWheat = {245, 222, 179, 255}; 273 | constexpr Color kColorWhitesmoke = {245, 245, 245, 255}; 274 | constexpr Color kColorYellow = {255, 255, 0, 255}; 275 | constexpr Color kColorYellowGreen = {154, 205, 50, 255}; 276 | 277 | } // namespace sft 278 | -------------------------------------------------------------------------------- /src/geometry/geometry.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "geometry.h" 7 | 8 | #include 9 | 10 | namespace sft { 11 | 12 | size_t TileFactorForAvailableHardwareConcurrency() { 13 | const auto cores = std::thread::hardware_concurrency(); 14 | auto factor = glm::log2(static_cast(cores)); 15 | factor = std::ceil(factor); 16 | factor += 1.0f; 17 | factor = std::max(2.0f, factor); 18 | return factor; 19 | } 20 | 21 | } // namespace sft 22 | -------------------------------------------------------------------------------- /src/geometry/geometry.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "color.h" 9 | #include "rect.h" 10 | #include "size.h" 11 | #include "types.h" 12 | 13 | namespace sft { 14 | 15 | template 16 | constexpr T BarycentricInterpolation(const T& p1, 17 | const T& p2, 18 | const T& p3, 19 | const glm::vec3& bary) { 20 | return bary.x * p1 + bary.y * p2 + bary.z * p3; 21 | } 22 | 23 | size_t TileFactorForAvailableHardwareConcurrency(); 24 | 25 | } // namespace sft 26 | -------------------------------------------------------------------------------- /src/geometry/rect.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "rect.h" 7 | 8 | namespace sft {} // namespace sft 9 | -------------------------------------------------------------------------------- /src/geometry/rect.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | 11 | #include "size.h" 12 | #include "types.h" 13 | 14 | namespace sft { 15 | 16 | struct Rect { 17 | glm::vec2 origin = {0, 0}; 18 | Size size; 19 | 20 | constexpr Rect() = default; 21 | 22 | constexpr Rect(glm::vec2 p_origin, Size p_size) 23 | : origin(p_origin), size(p_size) {} 24 | 25 | constexpr Rect(ScalarF x, ScalarF y, ScalarF width, ScalarF height) 26 | : origin({x, y}), size({width, height}) {} 27 | 28 | constexpr Rect(glm::vec2 sz) : origin({0, 0}), size(sz.x, sz.y) {} 29 | 30 | constexpr std::array GetLTRB() const { 31 | const auto left = std::min(origin.x, origin.x + size.width); 32 | const auto top = std::min(origin.y, origin.y + size.height); 33 | const auto right = std::max(origin.x, origin.x + size.width); 34 | const auto bottom = std::max(origin.y, origin.y + size.height); 35 | return {left, top, right, bottom}; 36 | } 37 | 38 | constexpr glm::vec2 GetLT() const { 39 | const auto left = std::min(origin.x, origin.x + size.width); 40 | const auto top = std::min(origin.y, origin.y + size.height); 41 | return {left, top}; 42 | } 43 | 44 | constexpr glm::vec2 GetRB() const { 45 | const auto right = std::max(origin.x, origin.x + size.width); 46 | const auto bottom = std::max(origin.y, origin.y + size.height); 47 | return {right, bottom}; 48 | } 49 | 50 | constexpr static Rect MakeLTRB(ScalarF left, 51 | ScalarF top, 52 | ScalarF right, 53 | ScalarF bottom) { 54 | return Rect(left, top, right - left, bottom - top); 55 | } 56 | 57 | constexpr std::optional Intersection(const Rect& o) const { 58 | auto this_ltrb = GetLTRB(); 59 | auto other_ltrb = o.GetLTRB(); 60 | auto intersection = 61 | Rect::MakeLTRB(std::max(this_ltrb[0], other_ltrb[0]), // 62 | std::max(this_ltrb[1], other_ltrb[1]), // 63 | std::min(this_ltrb[2], other_ltrb[2]), // 64 | std::min(this_ltrb[3], other_ltrb[3]) // 65 | ); 66 | if (intersection.size.IsEmpty()) { 67 | return std::nullopt; 68 | } 69 | return intersection; 70 | } 71 | }; 72 | 73 | } // namespace sft 74 | -------------------------------------------------------------------------------- /src/geometry/size.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "size.h" 7 | 8 | namespace sft {} // namespace sft 9 | -------------------------------------------------------------------------------- /src/geometry/size.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "types.h" 9 | 10 | namespace sft { 11 | 12 | struct Size { 13 | ScalarF width = 0.0; 14 | ScalarF height = 0.0; 15 | 16 | constexpr Size() = default; 17 | 18 | constexpr Size(ScalarF p_width, ScalarF p_height) 19 | : width(p_width), height(p_height) {} 20 | 21 | constexpr bool IsEmpty() const { return width * height <= 0.0; } 22 | 23 | constexpr ScalarF GetArea() const { return width * height; } 24 | }; 25 | 26 | } // namespace sft 27 | -------------------------------------------------------------------------------- /src/geometry/types.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "types.h" 7 | 8 | namespace sft {} 9 | -------------------------------------------------------------------------------- /src/geometry/types.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace sft { 17 | 18 | using Scalar = int32_t; 19 | using ScalarF = float; 20 | 21 | constexpr ScalarF kEpsilon = 1e-5; 22 | 23 | constexpr glm::vec2 kSampleMidpoint = {0.5f, 0.5f}; 24 | 25 | } // namespace sft 26 | -------------------------------------------------------------------------------- /src/model/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | sft_library(model 2 | model.cc 3 | model.h 4 | model_shader.cc 5 | model_shader.h 6 | ) 7 | 8 | target_link_libraries(model 9 | PUBLIC 10 | tinyobjloader 11 | rasterizer 12 | ) 13 | -------------------------------------------------------------------------------- /src/model/model.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "model.h" 7 | 8 | namespace sft { 9 | 10 | Model::Model(std::string path, std::string base_dir) 11 | : vertex_buffer_(Buffer::Create()) { 12 | std::string warnings; 13 | std::string errors; 14 | tinyobj::attrib_t attrib; 15 | std::vector shapes; 16 | std::vector materials; 17 | 18 | auto result = tinyobj::LoadObj(&attrib, &shapes, &materials, &warnings, 19 | &errors, path.c_str(), base_dir.c_str()); 20 | 21 | if (!warnings.empty()) { 22 | std::cout << warnings << std::endl; 23 | } 24 | 25 | if (!errors.empty()) { 26 | std::cout << errors << std::endl; 27 | } 28 | 29 | if (!result) { 30 | return; 31 | } 32 | 33 | bool has_normals = false; 34 | std::vector vertices; 35 | // Loop over shapes 36 | for (size_t s = 0; s < shapes.size(); s++) { 37 | // Loop over faces(polygon) 38 | size_t index_offset = 0; 39 | for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) { 40 | // Triangulation is on. 41 | size_t fv = 3; 42 | 43 | // Loop over vertices in the face. 44 | for (size_t v = 0; v < fv; v++) { 45 | // Access to vertex 46 | tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v]; 47 | 48 | // Vertex position 49 | glm::vec3 position(attrib.vertices[3 * idx.vertex_index + 0], 50 | attrib.vertices[3 * idx.vertex_index + 1], 51 | attrib.vertices[3 * idx.vertex_index + 2]); 52 | 53 | // Vertex normal 54 | glm::vec3 normal = {}; 55 | if (idx.normal_index >= 0) { 56 | has_normals |= true; 57 | normal.x = attrib.normals[3 * idx.normal_index + 0]; 58 | normal.y = attrib.normals[3 * idx.normal_index + 1]; 59 | normal.z = attrib.normals[3 * idx.normal_index + 2]; 60 | normal = glm::normalize(normal); 61 | } 62 | 63 | // Texture coords. 64 | glm::vec2 texture_coord; 65 | if (idx.texcoord_index >= 0) { 66 | texture_coord = 67 | glm::vec2({attrib.texcoords[2 * idx.texcoord_index + 0], 68 | attrib.texcoords[2 * idx.texcoord_index + 1]}); 69 | texture_coord = glm::abs(texture_coord); 70 | } 71 | 72 | vertices.push_back( 73 | ModelShader::VertexData{.position = position, 74 | .normal = normal, 75 | .texture_coord = texture_coord}); 76 | } 77 | index_offset += fv; 78 | } 79 | } 80 | 81 | if (!has_normals) { 82 | SFT_ASSERT(vertices.size() % 3 == 0); 83 | for (size_t i = 0; i < vertices.size(); i += 3) { 84 | auto& va = vertices[i + 0]; 85 | auto& vb = vertices[i + 1]; 86 | auto& vc = vertices[i + 2]; 87 | const auto a = va.position; 88 | const auto b = vb.position; 89 | const auto c = vc.position; 90 | auto normal = glm::normalize(glm::cross(c - a, b - a)); 91 | va.normal = vb.normal = vc.normal = normal; 92 | } 93 | } 94 | 95 | vertex_count_ = vertices.size(); 96 | vertex_buffer_->Emplace(std::move(vertices)); 97 | 98 | pipeline_ = std::make_shared(); 99 | pipeline_->depth_desc.depth_test_enabled = true; 100 | pipeline_->cull_face = CullFace::kBack; 101 | model_shader_ = std::make_shared(); 102 | pipeline_->shader = model_shader_; 103 | pipeline_->vertex_descriptor.offset = 104 | offsetof(ModelShader::VertexData, position); 105 | pipeline_->vertex_descriptor.stride = sizeof(ModelShader::VertexData); 106 | 107 | is_valid_ = true; 108 | } 109 | 110 | Model::~Model() = default; 111 | 112 | bool Model::IsValid() const { 113 | return is_valid_; 114 | } 115 | 116 | void Model::RenderTo(Rasterizer& rasterizer) { 117 | if (!IsValid()) { 118 | return; 119 | } 120 | 121 | if (!texture_) { 122 | return; 123 | } 124 | 125 | glm::vec2 size = rasterizer.GetSize(); 126 | 127 | auto proj = 128 | glm::perspectiveLH_ZO(glm::radians(90.f), size.x / size.y, 0.1f, 1000.0f); 129 | auto view = glm::lookAtLH(glm::vec3{0, 5, -10}, // eye 130 | glm::vec3{0, 0, 0}, // center 131 | glm::vec3{0, 1, 0} // up 132 | ); 133 | auto scale = glm::scale(glm::mat4{1.0f}, glm::vec3{scale_, scale_, scale_}); 134 | auto rotation = 135 | glm::rotate(glm::mat4{1.0f}, glm::radians(rotation_), {0, 1, 0}); 136 | auto model = scale * rotation; 137 | const auto mvp = proj * view * model; 138 | 139 | auto uniform_buffer = Buffer::Create(); 140 | uniform_buffer->Emplace(ModelShader::Uniforms{ 141 | .mvp = mvp, 142 | .light = light_direction_, 143 | .color = kColorFirebrick, 144 | }); 145 | sft::Uniforms uniforms; 146 | uniforms.buffer = *uniform_buffer; 147 | uniforms.images[0] = texture_; 148 | rasterizer.Draw(pipeline_, *vertex_buffer_, uniforms, vertex_count_); 149 | } 150 | 151 | void Model::SetScale(ScalarF scale) { 152 | scale_ = scale; 153 | } 154 | 155 | void Model::SetRotation(ScalarF rotation) { 156 | rotation_ = rotation; 157 | } 158 | 159 | void Model::SetTexture(std::shared_ptr texture) { 160 | texture_ = std::move(texture); 161 | } 162 | 163 | void Model::SetLightDirection(glm::vec3 dir) { 164 | light_direction_ = glm::normalize(dir); 165 | } 166 | 167 | Pipeline& Model::GetPipeline() { 168 | return *pipeline_; 169 | } 170 | 171 | } // namespace sft 172 | -------------------------------------------------------------------------------- /src/model/model.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "buffer.h" 15 | #include "geometry.h" 16 | #include "image.h" 17 | #include "model_shader.h" 18 | #include "rasterizer.h" 19 | 20 | namespace sft { 21 | 22 | class Model { 23 | public: 24 | Model(std::string path, std::string base_dir); 25 | 26 | ~Model(); 27 | 28 | bool IsValid() const; 29 | 30 | void RenderTo(Rasterizer& rasterizer); 31 | 32 | void SetScale(ScalarF scale); 33 | 34 | void SetRotation(ScalarF degrees); 35 | 36 | void SetTexture(std::shared_ptr texture); 37 | 38 | void SetLightDirection(glm::vec3 dir); 39 | 40 | Pipeline& GetPipeline(); 41 | 42 | private: 43 | std::shared_ptr model_shader_; 44 | std::shared_ptr pipeline_; 45 | std::shared_ptr vertex_buffer_; 46 | std::shared_ptr texture_; 47 | size_t vertex_count_ = 0u; 48 | ScalarF scale_ = 1.0f; 49 | ScalarF rotation_ = 0.0f; 50 | glm::vec3 light_direction_ = {0.0, 0.0, 1.0}; 51 | bool is_valid_ = false; 52 | 53 | Model(const Model&) = delete; 54 | Model& operator=(const Model&) = delete; 55 | }; 56 | 57 | } // namespace sft 58 | -------------------------------------------------------------------------------- /src/model/model_shader.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "model_shader.h" 7 | -------------------------------------------------------------------------------- /src/model/model_shader.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "image.h" 9 | #include "invocation.h" 10 | #include "shader.h" 11 | 12 | namespace sft { 13 | 14 | class ModelShader final : public Shader { 15 | public: 16 | struct VertexData { 17 | glm::vec3 position; 18 | glm::vec3 normal; 19 | glm::vec2 texture_coord; 20 | }; 21 | 22 | struct Uniforms { 23 | glm::mat4 mvp; 24 | glm::vec3 light; 25 | glm::vec4 color; 26 | }; 27 | 28 | struct Varyings { 29 | glm::vec2 texture_coord; 30 | glm::vec3 normal; 31 | }; 32 | 33 | ModelShader() = default; 34 | 35 | size_t GetVaryingsSize() const override { return sizeof(Varyings); } 36 | 37 | glm::vec4 ProcessVertex(const VertexInvocation& inv) const override { 38 | FORWARD(normal, normal); 39 | FORWARD(texture_coord, texture_coord); 40 | const auto mvp = UNIFORM(mvp); 41 | const auto pos = glm::vec4{VTX(position), 1.0}; 42 | return mvp * pos; 43 | } 44 | 45 | glm::vec4 ProcessFragment(const FragmentInvocation& inv) const override { 46 | auto normal = glm::vec4{VARYING_LOAD(normal), 1.0}; 47 | auto light = glm::vec4{UNIFORM(light), 1.0}; 48 | normal = glm::normalize(normal); 49 | light = glm::normalize(light); 50 | auto intensity = glm::dot(light, normal); 51 | auto intensity_color = glm::vec4{intensity, intensity, intensity, 1.0}; 52 | auto color = inv.LoadImage(0u).Sample(VARYING_LOAD(texture_coord)); 53 | color *= intensity_color; 54 | return color; 55 | } 56 | 57 | private: 58 | SFT_DISALLOW_COPY_AND_ASSIGN(ModelShader); 59 | }; 60 | 61 | } // namespace sft 62 | -------------------------------------------------------------------------------- /src/playground/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | sft_library(playground_lib 2 | ../../third_party/imgui/backends/imgui_impl_sdl.cpp 3 | ../../third_party/imgui/backends/imgui_impl_sdl.h 4 | ../../third_party/imgui/imgui.cpp 5 | ../../third_party/imgui/imgui.h 6 | ../../third_party/imgui/imgui_demo.cpp 7 | ../../third_party/imgui/imgui_draw.cpp 8 | ../../third_party/imgui/imgui_tables.cpp 9 | ../../third_party/imgui/imgui_widgets.cpp 10 | imgui_impl_sft.cc 11 | imgui_impl_sft.h 12 | imgui_shader.cc 13 | imgui_shader.h 14 | playground.cc 15 | playground.h 16 | sdl_utils.cc 17 | sdl_utils.h 18 | ) 19 | 20 | target_link_libraries(playground_lib 21 | PUBLIC 22 | SDL2-static 23 | canvas 24 | model 25 | ) 26 | 27 | target_include_directories(playground_lib 28 | PUBLIC 29 | ../../third_party/imgui 30 | ) 31 | 32 | sft_executable(playground 33 | unittests.cc 34 | playground_test.cc 35 | playground_test.h 36 | ) 37 | 38 | target_link_libraries(playground 39 | PRIVATE 40 | playground_lib 41 | gtest 42 | gtest_main 43 | ) 44 | 45 | sft_executable(playground_benchmark 46 | benchmarks.cc 47 | ) 48 | 49 | target_link_libraries(playground_benchmark 50 | PRIVATE 51 | benchmark_main 52 | rasterizer 53 | ) 54 | 55 | get_filename_component(SFT_ASSETS_LOCATION ../../fixtures ABSOLUTE) 56 | configure_file(fixtures_location.h.in fixtures_location.h @ONLY) 57 | target_include_directories(playground PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) 58 | target_include_directories(playground_benchmark PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) 59 | 60 | gtest_discover_tests(playground) 61 | -------------------------------------------------------------------------------- /src/playground/benchmarks.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "rasterizer.h" 3 | 4 | namespace sft { 5 | 6 | #define ADD_BENCHMARK(name) \ 7 | BENCHMARK_F(Benchmark, name)(benchmark::State & state) 8 | 9 | class BenchmarkFixture : public ::benchmark::Fixture { 10 | public: 11 | BenchmarkFixture() : scheduler_(marl::Scheduler::Config::allCores()) {} 12 | 13 | void SetUp(const ::benchmark::State& state) override { scheduler_.bind(); } 14 | 15 | void TearDown(const ::benchmark::State& state) override { 16 | scheduler_.unbind(); 17 | } 18 | 19 | private: 20 | marl::Scheduler scheduler_; 21 | 22 | SFT_DISALLOW_COPY_AND_ASSIGN(BenchmarkFixture); 23 | }; 24 | 25 | using Benchmark = BenchmarkFixture; 26 | 27 | constexpr glm::ivec2 kDefaultRasterizerSize = {1 << 14, 1 << 14}; 28 | 29 | ADD_BENCHMARK(DoNothing) { 30 | for (auto _ : state) { 31 | } 32 | } 33 | 34 | ADD_BENCHMARK(Clear) { 35 | Rasterizer rasterizer(kDefaultRasterizerSize, SampleCount::kFour); 36 | for (auto _ : state) { 37 | rasterizer.ResetMetrics(); 38 | rasterizer.Clear(kColorFirebrick); 39 | rasterizer.Finish(); 40 | } 41 | } 42 | 43 | } // namespace sft 44 | 45 | BENCHMARK_MAIN(); 46 | -------------------------------------------------------------------------------- /src/playground/fixtures_location.h.in: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #cmakedefine SFT_ASSETS_LOCATION "@SFT_ASSETS_LOCATION@" "/" 9 | -------------------------------------------------------------------------------- /src/playground/imgui_impl_sft.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "imgui_impl_sft.h" 7 | 8 | #include "backends/imgui_impl_sdl.h" 9 | #include "image.h" 10 | #include "imgui_shader.h" 11 | #include "macros.h" 12 | #include "rasterizer.h" 13 | 14 | namespace sft { 15 | 16 | struct RendererData { 17 | std::shared_ptr font_atlas; 18 | std::shared_ptr shader; 19 | std::shared_ptr pipeline; 20 | }; 21 | 22 | bool ImGui_ImplSFT_Init(SDL_Window* window, SDL_Renderer* renderer) { 23 | auto result = ImGui_ImplSDL2_InitForSDLRenderer(window, renderer); 24 | if (!result) { 25 | return false; 26 | } 27 | 28 | auto& io = ImGui::GetIO(); 29 | SFT_ASSERT(io.BackendRendererUserData == nullptr); 30 | SFT_ASSERT(io.BackendRendererName == nullptr); 31 | auto data = new RendererData{}; 32 | io.BackendRendererUserData = data; 33 | io.BackendRendererName = "SFT"; 34 | 35 | uint8_t* pixels = nullptr; 36 | int width = 0; 37 | int height = 0; 38 | io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); 39 | 40 | auto pipeline = std::make_shared(); 41 | auto shader = std::make_shared(); 42 | auto font_atlas = 43 | Image::Create(Mapping::MakeWithCopy(pixels, width * height * 4), 44 | glm::ivec2{width, height}); 45 | font_atlas->SetSampler({.min_mag_filter = Filter::kNearest}); 46 | 47 | io.Fonts->SetTexID(font_atlas.get()); 48 | 49 | pipeline->shader = shader; 50 | pipeline->color_desc.blend.enabled = true; 51 | pipeline->vertex_descriptor.offset = 52 | offsetof(ImGuiShader::VertexData, vertex_position); 53 | pipeline->vertex_descriptor.stride = sizeof(ImGuiShader::VertexData); 54 | pipeline->vertex_descriptor.vertex_format = VertexFormat::kFloat2; 55 | 56 | data->font_atlas = font_atlas; 57 | data->shader = shader; 58 | data->pipeline = pipeline; 59 | 60 | return true; 61 | } 62 | 63 | void ImGui_ImplSFT_NewFrame() { 64 | ImGui_ImplSDL2_NewFrame(); 65 | } 66 | 67 | ImGuiShader::VertexData ToShaderVertexData(const ImDrawVert& v) { 68 | ImGuiShader::VertexData result; 69 | result.vertex_color = Color{v.col}; 70 | result.vertex_position = {v.pos.x, v.pos.y}; 71 | result.texture_coordinates = {v.uv.x, v.uv.y}; 72 | return result; 73 | } 74 | 75 | constexpr Rect ToScissorRect(const ImVec2& display_size, 76 | const ImVec4& clip, 77 | const ImVec2& scale) { 78 | return Rect::MakeLTRB(clip.x * scale.x, // 79 | (display_size.y - clip.y) * scale.y, // 80 | clip.z * scale.x, // 81 | (display_size.y - clip.w) * scale.y // 82 | ); 83 | } 84 | 85 | void ImGui_ImplSFT_RenderDrawData(Rasterizer* rasterizer, ImDrawData* draw) { 86 | if (!draw || draw->CmdListsCount <= 0) { 87 | // Nothing to draw. 88 | return; 89 | } 90 | 91 | auto vtx_bytes = draw->TotalVtxCount * sizeof(ImDrawVert); 92 | auto idx_bytes = draw->TotalIdxCount * sizeof(ImDrawIdx); 93 | 94 | if (vtx_bytes == 0 || idx_bytes == 0) { 95 | // Nothing to draw. 96 | return; 97 | } 98 | 99 | auto& io = ImGui::GetIO(); 100 | SFT_ASSERT(io.BackendRendererUserData != nullptr); 101 | auto user_data = reinterpret_cast(io.BackendRendererUserData); 102 | 103 | auto vertex_buffer = Buffer::Create(); 104 | auto uniform_buffer = Buffer::Create(); 105 | 106 | SFT_ASSERT(draw->DisplayPos.x == 0 && draw->DisplayPos.y == 0); 107 | 108 | uniform_buffer->Emplace( 109 | {.mvp = glm::ortho(0, // left 110 | draw->DisplaySize.x, // right 111 | draw->DisplaySize.y, // bottom 112 | 0 // top 113 | )}); 114 | 115 | for (int i = 0; i < draw->CmdListsCount; i++) { 116 | const ImDrawList* draw_list = draw->CmdLists[i]; 117 | const auto& cmd_buffer = draw_list->CmdBuffer; 118 | const ImVector& vtx_buffer = draw_list->VtxBuffer; 119 | const ImVector& idx_buffer = draw_list->IdxBuffer; 120 | for (const auto& cmd : cmd_buffer) { 121 | if (cmd.UserCallback) { 122 | cmd.UserCallback(draw_list, &cmd); 123 | continue; 124 | } 125 | SFT_ASSERT(cmd.VtxOffset == 0); 126 | SFT_ASSERT(cmd.ElemCount % 3 == 0); 127 | 128 | user_data->pipeline->scissor = ToScissorRect( 129 | draw->DisplaySize, cmd.ClipRect, io.DisplayFramebufferScale); 130 | 131 | size_t index_offset = cmd.IdxOffset; 132 | 133 | const auto vtx_buffer_start = vertex_buffer->GetLength(); 134 | 135 | for (size_t e = 0; e < cmd.ElemCount; e += 3) { 136 | const auto v1_idx = idx_buffer[index_offset++]; 137 | const auto v2_idx = idx_buffer[index_offset++]; 138 | const auto v3_idx = idx_buffer[index_offset++]; 139 | 140 | const auto v1 = ToShaderVertexData(vtx_buffer[v1_idx]); 141 | const auto v2 = ToShaderVertexData(vtx_buffer[v2_idx]); 142 | const auto v3 = ToShaderVertexData(vtx_buffer[v3_idx]); 143 | 144 | vertex_buffer->Emplace(v1); 145 | vertex_buffer->Emplace(v2); 146 | vertex_buffer->Emplace(v3); 147 | } 148 | 149 | const auto vtx_buffer_end = vertex_buffer->GetLength(); 150 | 151 | auto texture = reinterpret_cast(cmd.GetTexID()); 152 | 153 | sft::Uniforms uniforms; 154 | uniforms.buffer = *uniform_buffer; 155 | uniforms.images[0u] = texture->shared_from_this(); 156 | 157 | rasterizer->Draw( 158 | user_data->pipeline, // pipeline 159 | BufferView{*vertex_buffer, vtx_buffer_start, 160 | vtx_buffer_end - vtx_buffer_start}, // vertex_buffer 161 | uniforms, // uniform buffer 162 | cmd.ElemCount // count 163 | ); 164 | } 165 | } 166 | } 167 | 168 | void ImGui_ImplSFT_Shutdown() { 169 | auto& io = ImGui::GetIO(); 170 | SFT_ASSERT(io.BackendRendererUserData != nullptr); 171 | SFT_ASSERT(io.BackendRendererName != nullptr); 172 | delete reinterpret_cast(io.BackendRendererUserData); 173 | io.BackendRendererUserData = nullptr; 174 | io.BackendRendererName = nullptr; 175 | ImGui_ImplSDL2_Shutdown(); 176 | } 177 | } // namespace sft 178 | -------------------------------------------------------------------------------- /src/playground/imgui_impl_sft.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include "imgui.h" 10 | 11 | namespace sft { 12 | 13 | class Rasterizer; 14 | 15 | bool ImGui_ImplSFT_Init(SDL_Window* window, SDL_Renderer* renderer); 16 | 17 | void ImGui_ImplSFT_NewFrame(); 18 | 19 | void ImGui_ImplSFT_RenderDrawData(Rasterizer* rasterizer, 20 | ImDrawData* draw_data); 21 | 22 | void ImGui_ImplSFT_Shutdown(); 23 | 24 | } // namespace sft 25 | -------------------------------------------------------------------------------- /src/playground/imgui_shader.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "imgui_shader.h" 7 | 8 | namespace sft {} 9 | -------------------------------------------------------------------------------- /src/playground/imgui_shader.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "image.h" 9 | #include "invocation.h" 10 | #include "macros.h" 11 | #include "shader.h" 12 | 13 | namespace sft { 14 | 15 | class ImGuiShader final : public Shader { 16 | public: 17 | struct VertexData { 18 | glm::vec2 vertex_position; 19 | glm::vec2 texture_coordinates; 20 | glm::vec4 vertex_color; 21 | }; 22 | 23 | struct Uniforms { 24 | glm::mat4 mvp; 25 | }; 26 | 27 | struct Varyings { 28 | glm::vec2 texture_coordinates; 29 | glm::vec4 vertex_color; 30 | }; 31 | 32 | ImGuiShader() = default; 33 | 34 | ~ImGuiShader() = default; 35 | 36 | size_t GetVaryingsSize() const override { return sizeof(Varyings); } 37 | 38 | glm::vec4 ProcessVertex(const VertexInvocation& inv) const override { 39 | FORWARD(texture_coordinates, texture_coordinates); 40 | FORWARD(vertex_color, vertex_color); 41 | auto pos = glm::vec4{VTX(vertex_position), 0.0, 1.0}; 42 | auto mvp = UNIFORM(mvp); 43 | return mvp * pos; 44 | } 45 | 46 | glm::vec4 ProcessFragment(const FragmentInvocation& inv) const override { 47 | const glm::vec4 texture_color = 48 | inv.LoadImage(0u).Sample(VARYING_LOAD(texture_coordinates)); 49 | const glm::vec4 color = VARYING_LOAD(vertex_color); 50 | return color * texture_color; 51 | } 52 | 53 | private: 54 | SFT_DISALLOW_COPY_AND_ASSIGN(ImGuiShader); 55 | }; 56 | 57 | } // namespace sft 58 | -------------------------------------------------------------------------------- /src/playground/playground.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "playground.h" 7 | 8 | #include 9 | #include 10 | 11 | #include "imgui_impl_sft.h" 12 | #include "rasterizer.h" 13 | #include "timing.h" 14 | 15 | namespace sft { 16 | 17 | static std::string CreateWindowTitle(MillisecondsF frame_time, 18 | const std::string& title) { 19 | std::stringstream stream; 20 | stream.precision(2); 21 | stream.setf(std::ios::fixed, std::ios::floatfield); 22 | if (!title.empty()) { 23 | stream << title; 24 | } else { 25 | stream << "SFT Sandbox"; 26 | } 27 | stream << " "; 28 | #ifndef NDEBUG 29 | stream << "DEBUG"; 30 | #else 31 | stream << "RELEASE"; 32 | #endif 33 | stream << " ("; 34 | stream << frame_time.count(); 35 | stream << " ms)"; 36 | return stream.str(); 37 | } 38 | 39 | Playground::Playground(glm::ivec2 size, SampleCount sample_count) 40 | : scheduler_(std::make_unique( 41 | marl::Scheduler::Config::allCores())), 42 | rasterizer_(std::make_shared(size, sample_count)), 43 | launch_time_(Clock::now()) { 44 | scheduler_->bind(); 45 | 46 | window_size_ = rasterizer_->GetSize(); 47 | 48 | Uint32 window_flags = 0; 49 | 50 | window_flags |= SDL_WINDOW_ALLOW_HIGHDPI; 51 | window_flags |= SDL_WINDOW_RESIZABLE; 52 | 53 | sdl_window_ = 54 | ::SDL_CreateWindow(CreateWindowTitle(MillisecondsF{0}, "").c_str(), 55 | SDL_WINDOWPOS_CENTERED, // 56 | SDL_WINDOWPOS_CENTERED, // 57 | window_size_.x, // 58 | window_size_.y, // 59 | window_flags // 60 | ); 61 | 62 | if (!sdl_window_) { 63 | return; 64 | } 65 | 66 | sdl_renderer_ = ::SDL_CreateRenderer(sdl_window_, -1, 0); 67 | 68 | if (!sdl_renderer_) { 69 | return; 70 | } 71 | 72 | // The size of the renderer and that of the rasterizer may differ in case of 73 | // Hi-DPI setups. Get the actual size and scale. 74 | glm::ivec2 output_size; 75 | if (::SDL_GetRendererOutputSize(sdl_renderer_, // 76 | &output_size.x, // 77 | &output_size.y // 78 | ) != 0) { 79 | return; 80 | } 81 | 82 | if (!rasterizer_->Resize(output_size)) { 83 | return; 84 | } 85 | 86 | ImGui::CreateContext(); 87 | auto result = ImGui_ImplSFT_Init(sdl_window_, sdl_renderer_); 88 | if (!result) { 89 | return; 90 | } 91 | 92 | is_valid_ = true; 93 | } 94 | 95 | Playground::~Playground() { 96 | ImGui_ImplSFT_Shutdown(); 97 | ImGui::DestroyContext(); 98 | 99 | if (sdl_window_) { 100 | ::SDL_DestroyWindow(sdl_window_); 101 | } 102 | if (sdl_renderer_) { 103 | ::SDL_DestroyRenderer(sdl_renderer_); 104 | } 105 | scheduler_->unbind(); 106 | } 107 | 108 | bool Playground::Render() { 109 | const auto result = OnRender(); 110 | 111 | const auto now = Clock::now(); 112 | if (std::chrono::duration_cast(now - last_title_update_) 113 | .count() > 500) { 114 | ::SDL_SetWindowTitle( 115 | sdl_window_, 116 | CreateWindowTitle( 117 | std::chrono::duration_cast(last_update_duration_), 118 | title_) 119 | .c_str()); 120 | last_title_update_ = now; 121 | } 122 | return result; 123 | } 124 | 125 | bool Playground::IsValid() const { 126 | return is_valid_; 127 | } 128 | 129 | bool Playground::Update() { 130 | if (!rasterizer_callback_) { 131 | return false; 132 | } 133 | return rasterizer_callback_(*rasterizer_); 134 | } 135 | 136 | static void DisplayMetrics(const RasterizerMetrics& m) { 137 | ImGui::Begin("Rasterizer Metrics"); 138 | 139 | ImGui::Text("Size: %d x %d", m.area.x, m.area.y); 140 | ImGui::Text("Draw Count: %zu", m.draw_count); 141 | ImGui::Text("Primitives: %zu", m.primitive_count); 142 | ImGui::Text("Primitives Processed: %zu (%.0f%%)", m.primitives_processed, 143 | m.primitives_processed * 100.f / m.primitive_count); 144 | ImGui::Text("Back Faces Culled: %zu (%.0f%%)", m.face_culling, 145 | m.face_culling * 100.f / m.primitive_count); 146 | ImGui::Text("Empty Primitive: %zu (%.0f%%)", m.empty_primitive, 147 | m.empty_primitive * 100.f / m.primitive_count); 148 | ImGui::Text("Scissor Culled: %zu (%.0f%%)", m.scissor_culling, 149 | m.scissor_culling * 100.f / m.primitive_count); 150 | ImGui::Text("Sample Point Culled: %zu (%.0f%%)", m.sample_point_culling, 151 | m.sample_point_culling * 100.f / m.primitive_count); 152 | ImGui::Text("Early Fragment Checks Tripped: %zu", m.early_fragment_test); 153 | ImGui::Text("Vertex Invocations: %zu", m.vertex_invocations); 154 | ImGui::Text( 155 | "Fragment Invocations: %zu (%.2fx screen)", m.fragment_invocations, 156 | static_cast(m.fragment_invocations) / (m.area.x * m.area.y)); 157 | 158 | ImGui::End(); 159 | } 160 | 161 | bool Playground::OnRender() { 162 | if (!is_valid_) { 163 | return false; 164 | } 165 | 166 | const auto update_start = Clock::now(); 167 | 168 | ImGui_ImplSFT_NewFrame(); 169 | ImGui::NewFrame(); 170 | 171 | rasterizer_->ResetMetrics(); 172 | 173 | if (!Update()) { 174 | return false; 175 | } 176 | 177 | DisplayMetrics(rasterizer_->GetMetrics()); 178 | rasterizer_->ResetMetrics(); 179 | 180 | ImGui::Render(); 181 | 182 | ImGui_ImplSFT_RenderDrawData(rasterizer_.get(), ImGui::GetDrawData()); 183 | 184 | rasterizer_->Finish(); 185 | 186 | const auto size = rasterizer_->GetSize(); 187 | 188 | auto& pass = rasterizer_->GetRenderPassAttachments(); 189 | std::shared_ptr> texture; 190 | if (pass.color.texture->GetSampleCount() == SampleCount::kOne) { 191 | texture = pass.color.texture; 192 | } else { 193 | if (!pass.color.texture->Resolve(*pass.color.resolve)) { 194 | return false; 195 | } 196 | texture = pass.color.resolve; 197 | } 198 | 199 | last_update_duration_ = Clock::now() - update_start; 200 | 201 | SDLTextureNoCopyCaster color_attachment(sdl_renderer_, // 202 | texture->Get({}, 0), // 203 | size.x, // 204 | size.y, // 205 | texture->GetBytesPerPixel() // 206 | ); 207 | 208 | SDL_Rect dest = {}; 209 | dest.x = 0; 210 | dest.y = 0; 211 | dest.w = size.x; 212 | dest.h = size.y; 213 | { 214 | if (::SDL_RenderCopyEx(sdl_renderer_, // 215 | color_attachment, // 216 | nullptr, // 217 | &dest, // 218 | 180, // 219 | NULL, // 220 | SDL_FLIP_HORIZONTAL // 221 | ) != 0) { 222 | return false; 223 | } 224 | } 225 | 226 | ::SDL_RenderPresent(sdl_renderer_); 227 | return true; 228 | } 229 | 230 | SecondsF Playground::GetTimeSinceLaunch() const { 231 | return Clock::now() - launch_time_; 232 | } 233 | 234 | bool Playground::OnWindowSizeChanged(glm::ivec2 size) { 235 | if (!rasterizer_) { 236 | return false; 237 | } 238 | return rasterizer_->Resize(size); 239 | } 240 | 241 | void Playground::SetTitle(std::string title) { 242 | title_ = std::move(title); 243 | } 244 | 245 | void Playground::SetRasterizerCallback(RasterizerCallback callback) { 246 | rasterizer_callback_ = callback; 247 | } 248 | 249 | } // namespace sft 250 | -------------------------------------------------------------------------------- /src/playground/playground.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include 11 | 12 | #include "geometry.h" 13 | #include "macros.h" 14 | #include "marl/scheduler.h" 15 | #include "sdl_utils.h" 16 | #include "texture.h" 17 | #include "timing.h" 18 | 19 | namespace sft { 20 | 21 | class Rasterizer; 22 | 23 | class Playground { 24 | public: 25 | Playground(glm::ivec2 size = {800, 600}, 26 | SampleCount sample_count = SampleCount::kOne); 27 | 28 | ~Playground(); 29 | 30 | bool IsValid() const; 31 | 32 | bool Render(); 33 | 34 | void SetTitle(std::string title); 35 | 36 | bool Update(); 37 | 38 | bool OnWindowSizeChanged(glm::ivec2 size); 39 | 40 | SecondsF GetTimeSinceLaunch() const; 41 | 42 | using RasterizerCallback = std::function; 43 | 44 | void SetRasterizerCallback(RasterizerCallback callback); 45 | 46 | private: 47 | std::unique_ptr scheduler_; 48 | const std::shared_ptr rasterizer_; 49 | glm::ivec2 window_size_; 50 | SDL_Window* sdl_window_ = nullptr; 51 | SDL_Renderer* sdl_renderer_ = nullptr; 52 | std::chrono::time_point launch_time_; 53 | std::chrono::time_point last_title_update_; 54 | std::chrono::nanoseconds last_update_duration_; 55 | std::string title_; 56 | RasterizerCallback rasterizer_callback_; 57 | bool is_valid_ = false; 58 | 59 | bool OnRender(); 60 | 61 | SFT_DISALLOW_COPY_AND_ASSIGN(Playground); 62 | }; 63 | 64 | } // namespace sft 65 | -------------------------------------------------------------------------------- /src/playground/playground_test.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "playground_test.h" 7 | 8 | #include 9 | 10 | #include 11 | 12 | #include "backends/imgui_impl_sdl.h" 13 | #include "fixtures_location.h" 14 | #include "imgui.h" 15 | #include "playground.h" 16 | 17 | namespace sft { 18 | 19 | PlaygroundTest::PlaygroundTest() : start_time_(Clock::now()) { 20 | SFT_ASSERT(::SDL_Init(SDL_INIT_VIDEO) == 0); 21 | } 22 | 23 | SecondsF PlaygroundTest::ElapsedTime() const { 24 | return Clock::now() - start_time_; 25 | } 26 | 27 | static std::string CreateTestName() { 28 | std::stringstream stream; 29 | stream << ::testing::UnitTest::GetInstance() 30 | ->current_test_info() 31 | ->test_suite_name(); 32 | stream << "."; 33 | stream << ::testing::UnitTest::GetInstance()->current_test_info()->name(); 34 | return stream.str(); 35 | } 36 | 37 | static bool gSkipRemainingTests = false; 38 | 39 | bool PlaygroundTest::Run(Playground& playground) const { 40 | bool is_running = true; 41 | bool success = true; 42 | playground.SetTitle(CreateTestName()); 43 | while (is_running) { 44 | success = is_running = playground.Render(); 45 | ::SDL_Event event; 46 | if (::SDL_PollEvent(&event) == 1) { 47 | auto& io = ImGui::GetIO(); 48 | 49 | ImGui_ImplSDL2_ProcessEvent(&event); 50 | 51 | switch (event.type) { 52 | case SDL_KEYUP: 53 | switch (event.key.keysym.sym) { 54 | case SDL_KeyCode::SDLK_q: 55 | case SDL_KeyCode::SDLK_ESCAPE: 56 | if ((event.key.keysym.mod & KMOD_LSHIFT) || 57 | (event.key.keysym.mod & KMOD_RSHIFT) || 58 | (event.key.keysym.mod & KMOD_LCTRL) || 59 | (event.key.keysym.mod & KMOD_RCTRL)) { 60 | gSkipRemainingTests = true; 61 | } 62 | is_running = false; 63 | break; 64 | default: 65 | break; 66 | } 67 | break; 68 | case SDL_QUIT: 69 | is_running = false; 70 | break; 71 | case SDL_WINDOWEVENT: 72 | switch (event.window.event) { 73 | case SDL_WINDOWEVENT_RESIZED: 74 | case SDL_WINDOWEVENT_SIZE_CHANGED: 75 | if (!playground.OnWindowSizeChanged( 76 | {event.window.data1, event.window.data2})) { 77 | std::cout << "Window resizing failed." << std::endl; 78 | is_running = false; 79 | } 80 | 81 | break; 82 | } 83 | break; 84 | } 85 | } 86 | } 87 | return success; 88 | } 89 | 90 | PlaygroundTest::~PlaygroundTest() { 91 | ::SDL_Quit(); 92 | } 93 | 94 | void PlaygroundTest::SetUp() { 95 | if (gSkipRemainingTests) { 96 | GTEST_SKIP(); 97 | } 98 | } 99 | 100 | void PlaygroundTest::TearDown() {} 101 | 102 | } // namespace sft 103 | -------------------------------------------------------------------------------- /src/playground/playground_test.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "gtest/gtest.h" 9 | #include "macros.h" 10 | #include "timing.h" 11 | 12 | namespace sft { 13 | 14 | class Playground; 15 | 16 | class PlaygroundTest : public ::testing::Test { 17 | public: 18 | PlaygroundTest(); 19 | 20 | ~PlaygroundTest(); 21 | 22 | bool Run(Playground& playground) const; 23 | 24 | SecondsF ElapsedTime() const; 25 | 26 | void SetUp() override; 27 | 28 | void TearDown() override; 29 | 30 | private: 31 | TimePoint start_time_; 32 | 33 | SFT_DISALLOW_COPY_AND_ASSIGN(PlaygroundTest); 34 | }; 35 | 36 | } // namespace sft 37 | -------------------------------------------------------------------------------- /src/playground/sdl_utils.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "sdl_utils.h" 7 | 8 | #include "geometry.h" 9 | 10 | namespace sft { 11 | 12 | SDLTextureNoCopyCaster::SDLTextureNoCopyCaster(SDL_Renderer* renderer, 13 | const void* pixels, 14 | int width, 15 | int height, 16 | int bytes_per_pixel) { 17 | if (pixels == nullptr) { 18 | return; 19 | } 20 | auto surface = 21 | ::SDL_CreateRGBSurfaceFrom(const_cast(pixels), // 22 | width, // 23 | height, // 24 | bytes_per_pixel * 8, // 25 | width * bytes_per_pixel, // 26 | kColorRed.WithAlpha(0).color, // r mask 27 | kColorGreen.WithAlpha(0).color, // g mask 28 | kColorBlue.WithAlpha(0).color, // b mask 29 | kColorTransparentBlack.color // a mask 30 | ); 31 | if (surface == NULL) { 32 | return; 33 | } 34 | surface_ = surface; 35 | auto texture = ::SDL_CreateTextureFromSurface(renderer, surface_); 36 | if (texture == NULL) { 37 | return; 38 | } 39 | texture_ = texture; 40 | } 41 | 42 | SDLTextureNoCopyCaster::~SDLTextureNoCopyCaster() { 43 | if (texture_) { 44 | ::SDL_DestroyTexture(texture_); 45 | } 46 | 47 | if (surface_) { 48 | ::SDL_FreeSurface(surface_); 49 | } 50 | } 51 | 52 | SDLTextureNoCopyCaster::operator bool() const { 53 | return static_cast(*this) != NULL; 54 | } 55 | 56 | SDLTextureNoCopyCaster::operator SDL_Texture*() const { 57 | return texture_; 58 | } 59 | 60 | } // namespace sft 61 | -------------------------------------------------------------------------------- /src/playground/sdl_utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include "macros.h" 10 | 11 | namespace sft { 12 | 13 | struct SDLTextureNoCopyCaster { 14 | SDLTextureNoCopyCaster(SDL_Renderer* renderer, 15 | const void* pixels, 16 | int width, 17 | int height, 18 | int bytes_per_pixel); 19 | 20 | ~SDLTextureNoCopyCaster(); 21 | 22 | operator bool() const; 23 | 24 | operator SDL_Texture*() const; 25 | 26 | private: 27 | SDL_Surface* surface_ = nullptr; 28 | SDL_Texture* texture_ = nullptr; 29 | 30 | SFT_DISALLOW_COPY_AND_ASSIGN(SDLTextureNoCopyCaster); 31 | }; 32 | 33 | } // namespace sft 34 | -------------------------------------------------------------------------------- /src/playground/unittests.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include 7 | 8 | #include "buffer.h" 9 | #include "canvas.h" 10 | #include "color_shader.h" 11 | #include "fixtures_location.h" 12 | #include "imgui.h" 13 | #include "model.h" 14 | #include "pipeline.h" 15 | #include "playground.h" 16 | #include "playground_test.h" 17 | #include "rasterizer.h" 18 | #include "texture_shader.h" 19 | #include "tiler.h" 20 | 21 | namespace sft { 22 | namespace testing { 23 | 24 | using RayTracerTest = PlaygroundTest; 25 | using RasterizerTest = PlaygroundTest; 26 | 27 | TEST_F(RasterizerTest, CanClearRasterizer) { 28 | Playground application; 29 | application.SetRasterizerCallback([](Rasterizer& rasterizer) -> bool { 30 | rasterizer.Clear(kColorFuchsia); 31 | return true; 32 | }); 33 | ASSERT_TRUE(Run(application)); 34 | } 35 | 36 | TEST_F(RasterizerTest, CanDrawTexturedImage) { 37 | Playground application; 38 | 39 | using VD = TextureShader::VertexData; 40 | using Uniforms = TextureShader::Uniforms; 41 | 42 | auto pipeline = std::make_shared(); 43 | auto shader = std::make_shared(); 44 | 45 | glm::vec3 p1 = {-0.7, 0.5, 0.0}; 46 | glm::vec3 p2 = {0.7, 0.5, 0.0}; 47 | glm::vec3 p3 = {0.7, -0.5, 0.0}; 48 | glm::vec3 p4 = {-0.7, -0.5, 0.0}; 49 | 50 | glm::vec2 tl = {0.0, 0.0}; 51 | glm::vec2 tr = {1.0, 0.0}; 52 | glm::vec2 br = {1.0, 1.0}; 53 | glm::vec2 bl = {0.0, 1.0}; 54 | auto vertex_buffer = Buffer::Create(); 55 | vertex_buffer->Emplace(std::vector{ 56 | {tl, p1}, 57 | {tr, p2}, 58 | {br, p3}, 59 | {br, p3}, 60 | {bl, p4}, 61 | {tl, p1}, 62 | }); 63 | 64 | auto image1 = Image::Create(SFT_ASSETS_LOCATION "airplane.jpg"); 65 | auto image2 = Image::Create(SFT_ASSETS_LOCATION "boston.jpg"); 66 | auto image3 = Image::Create(SFT_ASSETS_LOCATION "kalimba.jpg"); 67 | pipeline->shader = shader; 68 | pipeline->color_desc.blend.enabled = true; 69 | pipeline->vertex_descriptor.offset = offsetof(VD, position); 70 | pipeline->vertex_descriptor.stride = sizeof(VD); 71 | application.SetRasterizerCallback([&](Rasterizer& rasterizer) -> bool { 72 | rasterizer.Clear(kColorFirebrick); 73 | { 74 | auto uniform_buffer = Buffer::Create(); 75 | uniform_buffer->Emplace(Uniforms{ 76 | .alpha = 0.75, 77 | .offset = {0, 0}, 78 | }); 79 | sft::Uniforms uniforms; 80 | uniforms.buffer = *uniform_buffer; 81 | uniforms.images[0] = image1; 82 | rasterizer.Draw(pipeline, *vertex_buffer, uniforms, 6); 83 | } 84 | { 85 | auto uniform_buffer = Buffer::Create(); 86 | uniform_buffer->Emplace(Uniforms{ 87 | .alpha = 0.75, 88 | .offset = {0.2, 0.2}, 89 | }); 90 | sft::Uniforms uniforms; 91 | uniforms.buffer = *uniform_buffer; 92 | uniforms.images[0] = image2; 93 | rasterizer.Draw(pipeline, *vertex_buffer, uniforms, 6); 94 | } 95 | { 96 | auto uniform_buffer = Buffer::Create(); 97 | uniform_buffer->Emplace(Uniforms{ 98 | .alpha = 0.75, 99 | .offset = {0.4, 0.4}, 100 | }); 101 | sft::Uniforms uniforms; 102 | uniforms.buffer = *uniform_buffer; 103 | uniforms.images[0] = image3; 104 | rasterizer.Draw(pipeline, *vertex_buffer, uniforms, 6); 105 | } 106 | return true; 107 | }); 108 | ASSERT_TRUE(Run(application)); 109 | } 110 | 111 | TEST_F(RasterizerTest, CanDrawWithIndexBuffer16Bit) { 112 | Playground application; 113 | 114 | using VD = TextureShader::VertexData; 115 | using Uniforms = TextureShader::Uniforms; 116 | 117 | auto pipeline = std::make_shared(); 118 | auto shader = std::make_shared(); 119 | 120 | glm::vec3 p1 = {-0.7, 0.5, 0.0}; 121 | glm::vec3 p2 = {0.7, 0.5, 0.0}; 122 | glm::vec3 p3 = {0.7, -0.5, 0.0}; 123 | glm::vec3 p4 = {-0.7, -0.5, 0.0}; 124 | 125 | glm::vec2 tl = {0.0, 0.0}; 126 | glm::vec2 tr = {1.0, 0.0}; 127 | glm::vec2 br = {1.0, 1.0}; 128 | glm::vec2 bl = {0.0, 1.0}; 129 | 130 | auto buffer = Buffer::Create(); 131 | auto vertex_buffer = buffer->Emplace(std::vector{ 132 | {tl, p1}, // 0 133 | {tr, p2}, // 1 134 | {br, p3}, // 2 135 | {bl, p4}, // 3 136 | }); 137 | auto uniform_buffer = buffer->Emplace(Uniforms{ 138 | .alpha = 1.0, 139 | .offset = {0, 0}, 140 | }); 141 | sft::Uniforms uniforms; 142 | uniforms.buffer = uniform_buffer; 143 | uniforms.images[0] = Image::Create(SFT_ASSETS_LOCATION "embarcadero.jpg"); 144 | auto index_buffer = buffer->Emplace(std::vector{0, 1, 2, 2, 3, 0}); 145 | pipeline->shader = shader; 146 | pipeline->color_desc.blend.enabled = true; 147 | pipeline->vertex_descriptor.index_type = IndexType::kUInt16; 148 | pipeline->vertex_descriptor.offset = offsetof(VD, position); 149 | pipeline->vertex_descriptor.stride = sizeof(VD); 150 | application.SetRasterizerCallback([&](Rasterizer& rasterizer) -> bool { 151 | rasterizer.Clear(kColorFirebrick); 152 | rasterizer.Draw(pipeline, vertex_buffer, index_buffer, uniforms, 6); 153 | return true; 154 | }); 155 | ASSERT_TRUE(Run(application)); 156 | } 157 | 158 | // Compare with the outputs of 159 | // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators 160 | TEST_F(RasterizerTest, CanBlendWithDifferentModes) { 161 | Playground application; 162 | 163 | using VD = ColorShader::VertexData; 164 | using Uniforms = ColorShader::Uniforms; 165 | 166 | auto pipeline = std::make_shared(); 167 | auto shader = std::make_shared(); 168 | 169 | auto buffer = Buffer::Create(); 170 | 171 | auto index_buffer = buffer->Emplace(std::vector{0, 1, 2, 2, 3, 0}); 172 | auto src_vertex = buffer->Emplace(std::vector{ 173 | {{-0.25, 0.25, 0.0}}, 174 | {{1, 0.25, 0.0}}, 175 | {{1, -1, 0.0}}, 176 | {{-0.25, -1, 0.0}}, 177 | }); 178 | auto dst_vertex = buffer->Emplace(std::vector{ 179 | {{-1, 1, 0.0}}, 180 | {{0.25, 1.0, 0.0}}, 181 | {{0.25, -0.25, 0.0}}, 182 | {{-1, -0.25, 0.0}}, 183 | }); 184 | auto src_uniform = buffer->Emplace(Uniforms{ 185 | .color = kColorSkyBlue, 186 | }); 187 | auto dst_uniform = buffer->Emplace(Uniforms{ 188 | .color = kColorYellow, 189 | }); 190 | pipeline->shader = shader; 191 | pipeline->vertex_descriptor.offset = offsetof(VD, position); 192 | pipeline->vertex_descriptor.stride = sizeof(VD); 193 | application.SetRasterizerCallback([&](Rasterizer& rasterizer) -> bool { 194 | rasterizer.Clear(kColorWhite); 195 | const char* items[] = { 196 | "Clear", "Copy", "Destination", "SourceOver", 197 | "DestinationOver", "SourceIn", "DestinationIn", "SourceOut", 198 | "DestinationOut", "SourceAtop", "DestinationAtop", "XOR", 199 | }; 200 | static int selected = static_cast(BlendMode::kSourceOver); 201 | ImGui::Combo("Blend Mode", &selected, items, 202 | sizeof(items) / sizeof(const char*)); 203 | ImGui::TextWrapped( 204 | "Compare outputs to " 205 | "https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators"); 206 | pipeline->color_desc.blend = 207 | BlendDescriptorForMode(static_cast(selected)); 208 | rasterizer.Draw(pipeline, dst_vertex, index_buffer, dst_uniform, 6); 209 | rasterizer.Draw(pipeline, src_vertex, index_buffer, src_uniform, 6); 210 | return true; 211 | }); 212 | ASSERT_TRUE(Run(application)); 213 | } 214 | 215 | TEST_F(RasterizerTest, CanDrawWithIndexBuffer32Bit) { 216 | Playground application; 217 | 218 | using VD = TextureShader::VertexData; 219 | using Uniforms = TextureShader::Uniforms; 220 | 221 | auto pipeline = std::make_shared(); 222 | auto shader = std::make_shared(); 223 | 224 | glm::vec3 p1 = {-0.7, 0.5, 0.0}; 225 | glm::vec3 p2 = {0.7, 0.5, 0.0}; 226 | glm::vec3 p3 = {0.7, -0.5, 0.0}; 227 | glm::vec3 p4 = {-0.7, -0.5, 0.0}; 228 | 229 | glm::vec2 tl = {0.0, 0.0}; 230 | glm::vec2 tr = {1.0, 0.0}; 231 | glm::vec2 br = {1.0, 1.0}; 232 | glm::vec2 bl = {0.0, 1.0}; 233 | 234 | auto buffer = Buffer::Create(); 235 | auto vertex_buffer = buffer->Emplace(std::vector{ 236 | {tl, p1}, // 0 237 | {tr, p2}, // 1 238 | {br, p3}, // 2 239 | {bl, p4}, // 3 240 | }); 241 | auto uniform_buffer = buffer->Emplace(Uniforms{ 242 | .alpha = 1.0, 243 | .offset = {0, 0}, 244 | }); 245 | auto index_buffer = buffer->Emplace(std::vector{0, 1, 2, 2, 3, 0}); 246 | 247 | sft::Uniforms uniforms; 248 | uniforms.buffer = uniform_buffer; 249 | uniforms.images[0] = Image::Create(SFT_ASSETS_LOCATION "airplane.jpg"); 250 | pipeline->shader = shader; 251 | pipeline->color_desc.blend.enabled = true; 252 | pipeline->vertex_descriptor.index_type = IndexType::kUInt32; 253 | pipeline->vertex_descriptor.offset = offsetof(VD, position); 254 | pipeline->vertex_descriptor.stride = sizeof(VD); 255 | application.SetRasterizerCallback([&](Rasterizer& rasterizer) -> bool { 256 | rasterizer.Clear(kColorFirebrick); 257 | rasterizer.Draw(pipeline, vertex_buffer, index_buffer, uniforms, 6); 258 | return true; 259 | }); 260 | ASSERT_TRUE(Run(application)); 261 | } 262 | 263 | TEST_F(RasterizerTest, CanCompareLinearAndNearestSampling) { 264 | Playground application; 265 | 266 | using VD = TextureShader::VertexData; 267 | using Uniforms = TextureShader::Uniforms; 268 | 269 | auto pipeline = std::make_shared(); 270 | auto shader = std::make_shared(); 271 | 272 | glm::vec3 p1 = {-0.4, 0.4, 0.0}; 273 | glm::vec3 p2 = {0.4, 0.4, 0.0}; 274 | glm::vec3 p3 = {0.4, -0.4, 0.0}; 275 | glm::vec3 p4 = {-0.4, -0.4, 0.0}; 276 | 277 | glm::vec2 tl = {0.0, 0.0}; 278 | glm::vec2 tr = {1.0, 0.0}; 279 | glm::vec2 br = {1.0, 1.0}; 280 | glm::vec2 bl = {0.0, 1.0}; 281 | auto vertex_buffer = Buffer::Create(); 282 | vertex_buffer->Emplace(std::vector{ 283 | {tl, p1}, 284 | {tr, p2}, 285 | {br, p3}, 286 | {br, p3}, 287 | {bl, p4}, 288 | {tl, p1}, 289 | }); 290 | 291 | auto image1 = Image::Create(SFT_ASSETS_LOCATION "airplane.jpg"); 292 | image1->SetSampler({.min_mag_filter = Filter::kLinear}); 293 | auto image2 = Image::Create(SFT_ASSETS_LOCATION "airplane.jpg"); 294 | image2->SetSampler({.min_mag_filter = Filter::kNearest}); 295 | pipeline->shader = shader; 296 | pipeline->color_desc.blend.enabled = true; 297 | pipeline->vertex_descriptor.offset = offsetof(VD, position); 298 | pipeline->vertex_descriptor.stride = sizeof(VD); 299 | application.SetRasterizerCallback([&](Rasterizer& rasterizer) -> bool { 300 | rasterizer.Clear(kColorFirebrick); 301 | { 302 | auto uniform_buffer = Buffer::Create(); 303 | uniform_buffer->Emplace(Uniforms{ 304 | .alpha = 1.0, 305 | .offset = {0, -0.5}, 306 | }); 307 | sft::Uniforms uniforms; 308 | uniforms.buffer = *uniform_buffer; 309 | uniforms.images[0] = image1; 310 | rasterizer.Draw(pipeline, *vertex_buffer, uniforms, 6); 311 | } 312 | { 313 | auto uniform_buffer = Buffer::Create(); 314 | uniform_buffer->Emplace(Uniforms{ 315 | .alpha = 1.0, 316 | .offset = {0, 0.5}, 317 | }); 318 | sft::Uniforms uniforms; 319 | uniforms.buffer = *uniform_buffer; 320 | uniforms.images[0] = image2; 321 | rasterizer.Draw(pipeline, *vertex_buffer, uniforms, 6); 322 | } 323 | 324 | return true; 325 | }); 326 | ASSERT_TRUE(Run(application)); 327 | } 328 | 329 | TEST_F(RasterizerTest, CanWrapModeRepeatAndMirror) { 330 | Playground application; 331 | 332 | using VD = TextureShader::VertexData; 333 | using Uniforms = TextureShader::Uniforms; 334 | 335 | auto pipeline = std::make_shared(); 336 | auto shader = std::make_shared(); 337 | 338 | glm::vec3 p1 = {-0.7, 0.5, 0.0}; 339 | glm::vec3 p2 = {0.7, 0.5, 0.0}; 340 | glm::vec3 p3 = {0.7, -0.5, 0.0}; 341 | glm::vec3 p4 = {-0.7, -0.5, 0.0}; 342 | 343 | glm::vec2 tl = {0.0, 0.0}; 344 | glm::vec2 tr = {6.0, 0.0}; 345 | glm::vec2 br = {6.0, 6.0}; 346 | glm::vec2 bl = {0.0, 6.0}; 347 | auto buffer = Buffer::Create(); 348 | auto vertex_buffer = buffer->Emplace(std::vector{ 349 | {tl, p1}, 350 | {tr, p2}, 351 | {br, p3}, 352 | {br, p3}, 353 | {bl, p4}, 354 | {tl, p1}, 355 | }); 356 | auto uniform_buffer = buffer->Emplace(Uniforms{ 357 | .alpha = 1.0, 358 | .offset = {0, 0}, 359 | }); 360 | auto image1 = Image::Create(SFT_ASSETS_LOCATION "airplane.jpg"); 361 | Sampler sampler; 362 | sampler.wrap_mode_s = WrapMode::kRepeat; 363 | sampler.wrap_mode_t = WrapMode::kMirror; 364 | image1->SetSampler(sampler); 365 | sft::Uniforms uniforms; 366 | uniforms.buffer = uniform_buffer; 367 | uniforms.images[0] = image1; 368 | pipeline->shader = shader; 369 | pipeline->color_desc.blend.enabled = true; 370 | pipeline->vertex_descriptor.offset = offsetof(VD, position); 371 | pipeline->vertex_descriptor.stride = sizeof(VD); 372 | application.SetRasterizerCallback([&](Rasterizer& rasterizer) -> bool { 373 | rasterizer.Clear(kColorFirebrick); 374 | rasterizer.Draw(pipeline, vertex_buffer, uniforms, 6); 375 | return true; 376 | }); 377 | ASSERT_TRUE(Run(application)); 378 | } 379 | 380 | TEST_F(RasterizerTest, CanWrapModeClampAndRepeat) { 381 | Playground application; 382 | 383 | using VD = TextureShader::VertexData; 384 | using Uniforms = TextureShader::Uniforms; 385 | 386 | auto pipeline = std::make_shared(); 387 | auto shader = std::make_shared(); 388 | 389 | glm::vec3 p1 = {-0.7, 0.5, 0.0}; 390 | glm::vec3 p2 = {0.7, 0.5, 0.0}; 391 | glm::vec3 p3 = {0.7, -0.5, 0.0}; 392 | glm::vec3 p4 = {-0.7, -0.5, 0.0}; 393 | 394 | glm::vec2 tl = {0.0, 0.0}; 395 | glm::vec2 tr = {6.0, 0.0}; 396 | glm::vec2 br = {6.0, 6.0}; 397 | glm::vec2 bl = {0.0, 6.0}; 398 | auto buffer = Buffer::Create(); 399 | auto vertex_buffer = buffer->Emplace(std::vector{ 400 | {tl, p1}, 401 | {tr, p2}, 402 | {br, p3}, 403 | {br, p3}, 404 | {bl, p4}, 405 | {tl, p1}, 406 | }); 407 | auto uniform_buffer = buffer->Emplace(Uniforms{ 408 | .alpha = 1.0, 409 | .offset = {0, 0}, 410 | }); 411 | auto image1 = Image::Create(SFT_ASSETS_LOCATION "airplane.jpg"); 412 | Sampler sampler; 413 | sampler.wrap_mode_s = WrapMode::kClamp; 414 | sampler.wrap_mode_t = WrapMode::kMirror; 415 | image1->SetSampler(sampler); 416 | sft::Uniforms uniforms; 417 | uniforms.buffer = uniform_buffer; 418 | uniforms.images[0] = image1; 419 | pipeline->shader = shader; 420 | pipeline->color_desc.blend.enabled = true; 421 | pipeline->vertex_descriptor.offset = offsetof(VD, position); 422 | pipeline->vertex_descriptor.stride = sizeof(VD); 423 | application.SetRasterizerCallback([&](Rasterizer& rasterizer) -> bool { 424 | rasterizer.Clear(kColorFirebrick); 425 | rasterizer.Draw(pipeline, vertex_buffer, uniforms, 6); 426 | return true; 427 | }); 428 | ASSERT_TRUE(Run(application)); 429 | } 430 | 431 | TEST_F(RasterizerTest, CanDrawTeapot) { 432 | Playground application; 433 | Model model(SFT_ASSETS_LOCATION "teapot/teapot.obj", 434 | SFT_ASSETS_LOCATION "teapot"); 435 | model.SetScale(0.075); 436 | auto image = Image::Create(SFT_ASSETS_LOCATION "marble.jpg"); 437 | image->SetSampler({.min_mag_filter = Filter::kLinear}); 438 | model.SetTexture(image); 439 | ASSERT_TRUE(model.IsValid()); 440 | application.SetRasterizerCallback([&](Rasterizer& rasterizer) -> bool { 441 | rasterizer.Clear(kColorGray); 442 | model.SetRotation(application.GetTimeSinceLaunch().count() * 45); 443 | model.RenderTo(rasterizer); 444 | return true; 445 | }); 446 | ASSERT_TRUE(Run(application)); 447 | } 448 | 449 | TEST_F(RasterizerTest, CanMSAA) { 450 | Playground application({800, 600}, SampleCount::kFour); 451 | Model model(SFT_ASSETS_LOCATION "teapot/teapot.obj", 452 | SFT_ASSETS_LOCATION "teapot"); 453 | auto image = Image::Create(SFT_ASSETS_LOCATION "marble.jpg"); 454 | image->SetSampler({.min_mag_filter = Filter::kLinear}); 455 | model.SetTexture(image); 456 | ASSERT_TRUE(model.IsValid()); 457 | const char* msaa_items[] = { 458 | "No MSAA", // 459 | "2x MSAA", // 460 | "4x MSAA", // 461 | "8x MSAA", // 462 | "16x MSAA" // 463 | }; 464 | auto to_sample_count = [](int option) -> SampleCount { 465 | switch (option) { 466 | case 0: 467 | return SampleCount::kOne; 468 | case 1: 469 | return SampleCount::kTwo; 470 | case 2: 471 | return SampleCount::kFour; 472 | case 3: 473 | return SampleCount::kEight; 474 | case 4: 475 | return SampleCount::kSixteen; 476 | } 477 | return SampleCount::kOne; 478 | }; 479 | using VD = ColorShader::VertexData; 480 | using Uniforms = ColorShader::Uniforms; 481 | 482 | auto pipeline = std::make_shared(); 483 | pipeline->shader = std::make_shared(); 484 | pipeline->vertex_descriptor.offset = offsetof(VD, position); 485 | pipeline->vertex_descriptor.stride = sizeof(VD); 486 | auto buffer = Buffer::Create(); 487 | auto vertex_buffer = buffer->Emplace(std::vector{ 488 | VD{.position = {-1.0, -1.0, 0.0}}, 489 | VD{.position = {0.0, 1.0, 0.0}}, 490 | VD{.position = {1.0, -1.0, 0.0}}, 491 | }); 492 | auto uniform_buffer = buffer->Emplace(Uniforms{ 493 | .color = kColorFirebrick, 494 | }); 495 | 496 | application.SetRasterizerCallback([&](Rasterizer& rasterizer) -> bool { 497 | static int msaa_current = 2; 498 | ImGui::ListBox("MSAA Options", &msaa_current, msaa_items, 499 | IM_ARRAYSIZE(msaa_items)); 500 | SFT_ASSERT(rasterizer.GetRenderPassAttachments().SetSampleCount( 501 | to_sample_count(msaa_current))); 502 | rasterizer.Clear(kColorGray); 503 | static ScalarF rotation = 48.132; 504 | static ScalarF scale = 0.088; 505 | ImGui::SliderFloat("Rotation", &rotation, 0.f, 360.f); 506 | ImGui::SliderFloat("Scale", &scale, 0.075f / 2.0f, 0.075 * 2.0f); 507 | model.SetRotation(rotation); 508 | model.SetScale(scale); 509 | rasterizer.Draw(pipeline, vertex_buffer, uniform_buffer, 3u); 510 | model.RenderTo(rasterizer); 511 | return true; 512 | }); 513 | ASSERT_TRUE(Run(application)); 514 | } 515 | 516 | static void DisplayTextureInHUD(const char* title, 517 | Image* image, 518 | ScalarF scale) { 519 | if (!image) { 520 | return; 521 | } 522 | ImGui::Text(title, ""); 523 | auto& io = ImGui::GetIO(); 524 | const auto tint_color = ImVec4(1, 1, 1, 1); 525 | const auto border_color = ImVec4(1, 1, 1, 1); 526 | const auto tex_size = image->GetSize(); 527 | const auto image_pos = ImGui::GetCursorScreenPos(); 528 | const auto image_size = 529 | glm::vec2{tex_size.x * scale, tex_size.y * scale} / 530 | glm::vec2{io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y}; 531 | ImGui::Image(image, // 532 | ImVec2(image_size.x, image_size.y), // 533 | ImVec2(0, 1), // uv0 534 | ImVec2(1, 0), // uv1 535 | tint_color, // tint color 536 | border_color // border color 537 | ); 538 | if (ImGui::IsItemHovered()) { 539 | ImGui::BeginTooltip(); 540 | constexpr auto min_uv = glm::vec2{0.0f, 0.0f}; 541 | constexpr auto max_uv = glm::vec2{1.0f, 1.0f}; 542 | auto uv = glm::clamp( 543 | glm::vec2{io.MousePos.x - image_pos.x, io.MousePos.y - image_pos.y} / 544 | image_size, 545 | min_uv, max_uv); 546 | constexpr auto zoom_factor = 40.0f; 547 | auto zoom_uv_correction = glm::vec2{1.f / zoom_factor}; 548 | auto uv1 = glm::clamp(uv - zoom_uv_correction, min_uv, max_uv); 549 | auto uv2 = glm::clamp(uv + zoom_uv_correction, min_uv, max_uv); 550 | uv.y = 1.f - uv.y; 551 | uv1.y = 1.f - uv1.y; 552 | uv2.y = 1.f - uv2.y; 553 | auto tooltip_image_size = image_size / 2.0f; 554 | auto sampled = image->Sample(uv); 555 | ImGui::ColorEdit4("Color", (float*)&sampled); 556 | ImGui::Text("%s (%.2fx)", title, zoom_factor); 557 | ImGui::Image(image, // texture 558 | ImVec2(tooltip_image_size.x, tooltip_image_size.y), // size 559 | ImVec2{uv1.x, uv1.y}, // uv1 560 | ImVec2{uv2.x, uv2.y}, // uv2 561 | tint_color, // tint color 562 | border_color // border color 563 | ); 564 | ImGui::EndTooltip(); 565 | } 566 | } 567 | 568 | TEST_F(RasterizerTest, CanDrawHelmet) { 569 | Playground application; 570 | Model model(SFT_ASSETS_LOCATION "helmet/Helmet.obj", 571 | SFT_ASSETS_LOCATION "helmet"); 572 | 573 | auto image = Image::Create(SFT_ASSETS_LOCATION "helmet/Base.png"); 574 | image->SetSampler({.min_mag_filter = Filter::kNearest}); 575 | model.SetTexture(image); 576 | model.GetPipeline().stencil_desc = StencilAttachmentDescriptor{ 577 | .stencil_test_enabled = true, 578 | .stencil_compare = CompareFunction::kAlways, 579 | .depth_stencil_pass = StencilOperation::kIncrementClamp, 580 | }; 581 | ASSERT_TRUE(model.IsValid()); 582 | application.SetRasterizerCallback([&](Rasterizer& rasterizer) -> bool { 583 | rasterizer.Clear(kColorGray); 584 | static float rotation = 45 + 90; 585 | static float scale = 4; 586 | ImGui::SliderFloat("Rotation", &rotation, 0, 360); 587 | ImGui::SliderFloat("Scale", &scale, 0.1f, 25.0f); 588 | model.SetRotation(rotation); 589 | model.SetScale(scale); 590 | model.RenderTo(rasterizer); 591 | return true; 592 | }); 593 | ASSERT_TRUE(Run(application)); 594 | } 595 | 596 | TEST_F(RasterizerTest, CanCullFaces) { 597 | Playground application; 598 | 599 | using VD = ColorShader::VertexData; 600 | using Uniforms = ColorShader::Uniforms; 601 | 602 | auto pipeline = std::make_shared(); 603 | pipeline->shader = std::make_shared(); 604 | pipeline->vertex_descriptor.offset = offsetof(VD, position); 605 | pipeline->vertex_descriptor.stride = sizeof(VD); 606 | // This should be culled and nothing will show up. 607 | pipeline->cull_face = CullFace::kBack; 608 | pipeline->winding = Winding::kCounterClockwise; 609 | auto buffer = Buffer::Create(); 610 | auto vertex_buffer = buffer->Emplace(std::vector{ 611 | VD{.position = {-1.0, -1.0, 0.0}}, 612 | VD{.position = {0.0, 1.0, 0.0}}, 613 | VD{.position = {1.0, -1.0, 0.0}}, 614 | }); 615 | auto uniform_buffer = buffer->Emplace(Uniforms{ 616 | .color = kColorFirebrick, 617 | }); 618 | application.SetRasterizerCallback([&](Rasterizer& rasterizer) -> bool { 619 | rasterizer.Clear(kColorBeige); 620 | rasterizer.Draw(pipeline, vertex_buffer, uniform_buffer, 3u); 621 | return true; 622 | }); 623 | ASSERT_TRUE(Run(application)); 624 | } 625 | 626 | TEST_F(RasterizerTest, CanApplyScissor) { 627 | Playground application; 628 | 629 | using VD = ColorShader::VertexData; 630 | using Uniforms = ColorShader::Uniforms; 631 | 632 | auto pipeline = std::make_shared(); 633 | pipeline->shader = std::make_shared(); 634 | pipeline->vertex_descriptor.offset = offsetof(VD, position); 635 | pipeline->vertex_descriptor.stride = sizeof(VD); 636 | auto buffer = Buffer::Create(); 637 | auto vertex_buffer = buffer->Emplace(std::vector{ 638 | VD{.position = {-1.0, -1.0, 0.0}}, 639 | VD{.position = {0.0, 1.0, 0.0}}, 640 | VD{.position = {1.0, -1.0, 0.0}}, 641 | }); 642 | auto uniform_buffer = buffer->Emplace(Uniforms{ 643 | .color = kColorFirebrick, 644 | }); 645 | application.SetRasterizerCallback([&](Rasterizer& rasterizer) -> bool { 646 | rasterizer.Clear(kColorBeige); 647 | const auto sz = rasterizer.GetSize(); 648 | pipeline->scissor = Rect{sz.x / 2.0f, 0, sz.x / 2.0f, sz.y / 2.0f}; 649 | rasterizer.Draw(pipeline, vertex_buffer, uniform_buffer, 3u); 650 | return true; 651 | }); 652 | ASSERT_TRUE(Run(application)); 653 | } 654 | 655 | TEST_F(RasterizerTest, CanDrawToDepthBuffer) { 656 | Playground application; 657 | 658 | using VD = ColorShader::VertexData; 659 | using Uniforms = ColorShader::Uniforms; 660 | 661 | auto pipeline = std::make_shared(); 662 | pipeline->shader = std::make_shared(); 663 | pipeline->vertex_descriptor.offset = offsetof(VD, position); 664 | pipeline->vertex_descriptor.stride = sizeof(VD); 665 | pipeline->depth_desc.depth_test_enabled = true; 666 | auto vertex_buffer = Buffer::Create(); 667 | auto uniform_buffer = Buffer::Create(); 668 | vertex_buffer->Emplace(std::vector{ 669 | VD{.position = {-1.0, -1.0, -1.0}}, 670 | VD{.position = {0.0, 1.0, 1.0}}, 671 | VD{.position = {1.0, -1.0, -1.0}}, 672 | }); 673 | uniform_buffer->Emplace(Uniforms{ 674 | .color = kColorFuchsia, 675 | }); 676 | application.SetRasterizerCallback([&](Rasterizer& rasterizer) -> bool { 677 | rasterizer.Clear(kColorBeige); 678 | rasterizer.Draw(pipeline, *vertex_buffer, BufferView{*uniform_buffer}, 3u); 679 | return true; 680 | }); 681 | ASSERT_TRUE(Run(application)); 682 | } 683 | 684 | TEST_F(RasterizerTest, CanPerformDepthTest) { 685 | Playground application; 686 | 687 | using VD = ColorShader::VertexData; 688 | using Uniforms = ColorShader::Uniforms; 689 | 690 | auto pipeline = std::make_shared(); 691 | pipeline->shader = std::make_shared(); 692 | pipeline->vertex_descriptor.offset = offsetof(VD, position); 693 | pipeline->vertex_descriptor.stride = sizeof(VD); 694 | 695 | auto buffer = Buffer::Create(); 696 | auto vertex_buffer1 = buffer->Emplace(std::vector{ 697 | VD{.position = {-1.0, -1.0, -1.0}}, 698 | VD{.position = {0.0, 1.0, 1.0}}, 699 | VD{.position = {1.0, -1.0, -1.0}}, 700 | }); 701 | auto vertex_buffer2 = buffer->Emplace(std::vector{ 702 | VD{.position = {-1.0, 1.0, -1.0}}, // front 703 | VD{.position = {1.0, 1.0, -1.0}}, // front 704 | VD{.position = {0.0, -1.0, 1.0}}, // back 705 | }); 706 | auto uniform_buffer1 = buffer->Emplace(Uniforms{ 707 | .color = kColorFuchsia, 708 | }); 709 | auto uniform_buffer2 = buffer->Emplace(Uniforms{ 710 | .color = kColorFirebrick, 711 | }); 712 | application.SetRasterizerCallback([&](Rasterizer& rasterizer) -> bool { 713 | rasterizer.Clear(kColorBeige); 714 | pipeline->depth_desc.depth_test_enabled = true; 715 | rasterizer.Draw(pipeline, vertex_buffer1, uniform_buffer1, 3u); 716 | rasterizer.Draw(pipeline, vertex_buffer2, uniform_buffer2, 3u); 717 | return true; 718 | }); 719 | ASSERT_TRUE(Run(application)); 720 | } 721 | 722 | TEST_F(RasterizerTest, CanShowHUD) { 723 | Playground application; 724 | application.SetRasterizerCallback([](Rasterizer& rasterizer) -> bool { 725 | rasterizer.Clear(kColorGray); 726 | ImGui::ShowDemoWindow(); 727 | return true; 728 | }); 729 | ASSERT_TRUE(Run(application)); 730 | } 731 | 732 | TEST_F(RasterizerTest, CanvasCanDrawStuff) { 733 | Playground application; 734 | auto context = CanvasContextCreate(); 735 | application.SetRasterizerCallback([&](Rasterizer& rasterizer) -> bool { 736 | rasterizer.Clear(kColorWhite); 737 | Canvas canvas(context); 738 | Paint paint; 739 | paint.color = kColorRed; 740 | canvas.Translate({10, 10}); 741 | canvas.DrawRect(rasterizer, Rect({100, 100}), paint); 742 | paint.color = kColorGreen; 743 | canvas.Translate({100, 100}); 744 | canvas.DrawRect(rasterizer, Rect({100, 100}), paint); 745 | paint.color = kColorBlue; 746 | canvas.Translate({100, 100}); 747 | canvas.DrawRect(rasterizer, Rect({100, 100}), paint); 748 | return true; 749 | }); 750 | ASSERT_TRUE(Run(application)); 751 | } 752 | 753 | TEST_F(RasterizerTest, CanStencil) { 754 | Playground application; 755 | auto context = CanvasContextCreate(); 756 | 757 | application.SetRasterizerCallback([&](Rasterizer& rasterizer) -> bool { 758 | rasterizer.Clear(kColorWhite); 759 | Canvas canvas(context); 760 | Paint paint; 761 | paint.color = kColorRed.WithAlpha(128); 762 | paint.color_desc = ColorAttachmentDescriptor{.blend = {.enabled = true}}; 763 | paint.stencil_desc = StencilAttachmentDescriptor{ 764 | .stencil_test_enabled = true, 765 | .stencil_compare = CompareFunction::kAlways, 766 | .depth_stencil_pass = StencilOperation::kIncrementClamp, 767 | }; 768 | canvas.Translate({10, 10}); 769 | const auto rect = Rect{{300, 300}}; 770 | const auto offset = glm::vec2{100, 100}; 771 | canvas.DrawRect(rasterizer, rect, paint); 772 | paint.color = kColorGreen.WithAlpha(128); 773 | canvas.Translate(offset); 774 | canvas.DrawRect(rasterizer, rect, paint); 775 | paint.color = kColorBlue.WithAlpha(128); 776 | canvas.Translate(offset); 777 | canvas.DrawRect(rasterizer, rect, paint); 778 | return true; 779 | }); 780 | ASSERT_TRUE(Run(application)); 781 | } 782 | 783 | TEST_F(RasterizerTest, CanClipWithStencils) { 784 | Playground application; 785 | auto context = CanvasContextCreate(); 786 | application.SetRasterizerCallback([&](Rasterizer& rasterizer) -> bool { 787 | rasterizer.Clear(kColorWhite); 788 | Canvas canvas(context); 789 | canvas.Translate({10, 10}); 790 | 791 | Paint paint; 792 | 793 | // Paint the clip. 794 | paint.color = kColorRed; 795 | paint.stencil_desc = StencilAttachmentDescriptor{ 796 | .stencil_test_enabled = true, 797 | .stencil_compare = CompareFunction::kAlways, 798 | .depth_stencil_pass = StencilOperation::kIncrementClamp, 799 | }; 800 | 801 | const auto rect = Rect{{300, 300}}; 802 | const auto offset = glm::vec2{200, 100}; 803 | canvas.DrawRect(rasterizer, rect, paint); 804 | 805 | // Draw the box into the clip region. 806 | paint.color_desc = std::nullopt; 807 | paint.stencil_desc = StencilAttachmentDescriptor{ 808 | .stencil_test_enabled = true, 809 | .stencil_compare = CompareFunction::kGreaterEqual, 810 | .depth_stencil_pass = StencilOperation::kKeep, 811 | }; 812 | paint.stencil_reference = 1; 813 | paint.color = kColorGreen; 814 | canvas.Translate(offset); 815 | canvas.DrawRect(rasterizer, rect, paint); 816 | 817 | return true; 818 | }); 819 | ASSERT_TRUE(Run(application)); 820 | } 821 | 822 | } // namespace testing 823 | } // namespace sft 824 | -------------------------------------------------------------------------------- /src/rasterizer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | sft_library(rasterizer 2 | attachment.cc 3 | attachment.h 4 | blend.cc 5 | blend.h 6 | image.cc 7 | image.h 8 | invocation.cc 9 | invocation.h 10 | pipeline.cc 11 | pipeline.h 12 | rasterizer.cc 13 | rasterizer.h 14 | rasterizer_metrics.cc 15 | rasterizer_metrics.h 16 | render_pass.cc 17 | render_pass.h 18 | sampler.cc 19 | sampler.h 20 | shader.cc 21 | shader.h 22 | stage_resources.cc 23 | stage_resources.h 24 | texture.cc 25 | texture.h 26 | tiler.cc 27 | tiler.h 28 | uniforms.cc 29 | uniforms.h 30 | vertex_descriptor.cc 31 | vertex_descriptor.h 32 | ) 33 | 34 | target_link_libraries(rasterizer 35 | PUBLIC 36 | core 37 | geometry 38 | marl 39 | superliminal 40 | ) 41 | 42 | target_include_directories(rasterizer 43 | PUBLIC 44 | ../../third_party/stb 45 | ) 46 | -------------------------------------------------------------------------------- /src/rasterizer/attachment.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "attachment.h" 7 | 8 | namespace sft {} 9 | -------------------------------------------------------------------------------- /src/rasterizer/attachment.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | 11 | #include "blend.h" 12 | #include "geometry.h" 13 | #include "macros.h" 14 | 15 | namespace sft { 16 | 17 | enum class CompareFunction { 18 | /// Comparison test never passes. 19 | kNever, 20 | /// Comparison test passes always passes. 21 | kAlways, 22 | /// Comparison test passes if new_value < current_value. 23 | kLess, 24 | /// Comparison test passes if new_value == current_value. 25 | kEqual, 26 | /// Comparison test passes if new_value <= current_value. 27 | kLessEqual, 28 | /// Comparison test passes if new_value > current_value. 29 | kGreater, 30 | /// Comparison test passes if new_value != current_value. 31 | kNotEqual, 32 | /// Comparison test passes if new_value >= current_value. 33 | kGreaterEqual, 34 | }; 35 | 36 | template 37 | constexpr bool CompareFunctionPasses(CompareFunction comp, 38 | const T& lhs, 39 | const T& rhs) { 40 | switch (comp) { 41 | case CompareFunction::kNever: 42 | return false; 43 | case CompareFunction::kAlways: 44 | return true; 45 | case CompareFunction::kLess: 46 | return lhs < rhs; 47 | case CompareFunction::kEqual: 48 | return lhs == rhs; 49 | case CompareFunction::kLessEqual: 50 | return lhs <= rhs; 51 | case CompareFunction::kGreater: 52 | return lhs > rhs; 53 | case CompareFunction::kNotEqual: 54 | return lhs != rhs; 55 | case CompareFunction::kGreaterEqual: 56 | return lhs >= rhs; 57 | } 58 | return true; 59 | } 60 | 61 | enum class StencilOperation { 62 | /// Don't modify the current stencil value. 63 | kKeep, 64 | /// Reset the stencil value to zero. 65 | kZero, 66 | /// Reset the stencil value to the reference value. 67 | kSetToReferenceValue, 68 | /// Increment the current stencil value by 1. Clamp it to the maximum. 69 | kIncrementClamp, 70 | /// Decrement the current stencil value by 1. Clamp it to zero. 71 | kDecrementClamp, 72 | /// Perform a logical bitwise invert on the current stencil value. 73 | kInvert, 74 | /// Increment the current stencil value by 1. If at maximum, set to zero. 75 | kIncrementWrap, 76 | /// Decrement the current stencil value by 1. If at zero, set to maximum. 77 | kDecrementWrap, 78 | }; 79 | 80 | template >> 81 | T StencilOperationPerform(StencilOperation op, 82 | const T& current_value, 83 | const T& reference_value) { 84 | switch (op) { 85 | case StencilOperation::kKeep: 86 | return current_value; 87 | case StencilOperation::kZero: 88 | return {0}; 89 | case StencilOperation::kSetToReferenceValue: 90 | return reference_value; 91 | case StencilOperation::kIncrementClamp: 92 | if (current_value == std::numeric_limits::max()) { 93 | return std::numeric_limits::max(); 94 | } else { 95 | return current_value + 1; 96 | } 97 | case StencilOperation::kDecrementClamp: 98 | if (current_value == 0) { 99 | return 0; 100 | } else { 101 | return current_value - 1; 102 | } 103 | case StencilOperation::kInvert: 104 | return ~current_value; 105 | case StencilOperation::kIncrementWrap: 106 | if (current_value == std::numeric_limits::max()) { 107 | return 0; 108 | } else { 109 | return current_value + 1; 110 | } 111 | case StencilOperation::kDecrementWrap: 112 | if (current_value == 0) { 113 | return std::numeric_limits::max(); 114 | } else { 115 | return current_value - 1; 116 | } 117 | } 118 | return current_value; 119 | } 120 | 121 | struct ColorAttachmentDescriptor { 122 | BlendDescriptor blend; 123 | }; 124 | 125 | struct DepthAttachmentDescriptor { 126 | //---------------------------------------------------------------------------- 127 | /// Indicates if the depth test must be performed. If disabled, all access to 128 | /// the depth buffer is disabled. 129 | /// 130 | bool depth_test_enabled = false; 131 | //---------------------------------------------------------------------------- 132 | /// Indicates how to compare the value with that in the depth buffer. 133 | /// 134 | CompareFunction depth_compare = CompareFunction::kLessEqual; 135 | //---------------------------------------------------------------------------- 136 | /// Indicates when writes must be performed to the depth buffer. 137 | /// 138 | bool depth_write_enabled = true; 139 | }; 140 | 141 | struct StencilAttachmentDescriptor { 142 | //---------------------------------------------------------------------------- 143 | /// Indicates if the stencil test must be performed. If disabled, all access 144 | /// to the stencil buffer is disabled. 145 | /// 146 | bool stencil_test_enabled = false; 147 | //---------------------------------------------------------------------------- 148 | /// Indicates the operation to perform between the reference value and the 149 | /// value in the stencil buffer. Both values have the read_mask applied to 150 | /// them before performing this operation. 151 | /// 152 | CompareFunction stencil_compare = CompareFunction::kAlways; 153 | //---------------------------------------------------------------------------- 154 | /// Indicates what to do when the stencil test has failed. 155 | /// 156 | StencilOperation stencil_failure = StencilOperation::kKeep; 157 | //---------------------------------------------------------------------------- 158 | /// Indicates what to do when the stencil test passes but the depth test 159 | /// fails. 160 | /// 161 | StencilOperation depth_failure = StencilOperation::kKeep; 162 | //---------------------------------------------------------------------------- 163 | /// Indicates what to do when both the stencil and depth tests pass. 164 | /// 165 | StencilOperation depth_stencil_pass = StencilOperation::kKeep; 166 | //---------------------------------------------------------------------------- 167 | /// The mask applied to the reference and stencil buffer values before 168 | /// performing the stencil_compare operation. 169 | /// 170 | uint32_t read_mask = ~0u; 171 | //---------------------------------------------------------------------------- 172 | /// The mask applied to the new stencil value before it is written into the 173 | /// stencil buffer. 174 | /// 175 | uint32_t write_mask = ~0u; 176 | 177 | constexpr StencilOperation SelectOperation(bool depth_pass, 178 | bool stencil_pass) const { 179 | if (stencil_pass) { 180 | if (depth_pass) { 181 | return depth_stencil_pass; 182 | } else { 183 | return depth_failure; 184 | } 185 | } else { 186 | return stencil_failure; 187 | } 188 | } 189 | }; 190 | 191 | } // namespace sft 192 | -------------------------------------------------------------------------------- /src/rasterizer/blend.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "blend.h" 7 | -------------------------------------------------------------------------------- /src/rasterizer/blend.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "geometry.h" 9 | #include "macros.h" 10 | 11 | namespace sft { 12 | 13 | enum class BlendOp { 14 | kAdd, 15 | kSubtract, 16 | kReverseSubtract, 17 | kMin, 18 | kMax, 19 | }; 20 | 21 | enum class BlendFactor { 22 | kZero, 23 | kOne, 24 | kSourceColor, 25 | kOneMinusSourceColor, 26 | kSourceAlpha, 27 | kOneMinusSourceAlpha, 28 | kDestinationColor, 29 | kOneMinusDestinationColor, 30 | kDestinationAlpha, 31 | kOneMinusDestinationAlpha, 32 | kSourceAlphaSaturated, 33 | }; 34 | 35 | enum ColorMask : uint8_t { 36 | kRed = 1 << 0, 37 | kGreen = 1 << 1, 38 | kBlue = 1 << 2, 39 | kAlpha = 1 << 3, 40 | kAll = kRed | kGreen | kBlue | kAlpha, 41 | }; 42 | 43 | /// Specify how new (src) fragments should be combined with fragments already in 44 | /// the framebuffer (dst). 45 | /// 46 | /// ``` 47 | /// if (blending_enabled) { 48 | /// new_color.rgb = (src_color_fac * src_color.rgb) 49 | /// 50 | /// (dst_color_fac * dst_color.rgb); 51 | /// new_color.a = (src_alpha_fac * src_color.a) 52 | /// 53 | /// (dst_alpha_fac * dst_color.a); 54 | /// } else { 55 | /// new_color = src_color; 56 | /// } 57 | /// IMPORTANT: The write mask is applied irrespective of whether 58 | /// blending_enabled is set. 59 | /// new_color = new_color & write_mask; 60 | /// ``` 61 | struct BlendDescriptor { 62 | bool enabled = false; 63 | 64 | BlendFactor src_color_fac = BlendFactor::kSourceAlpha; 65 | BlendOp color_op = BlendOp::kAdd; 66 | BlendFactor dst_color_fac = BlendFactor::kOneMinusSourceAlpha; 67 | 68 | BlendFactor src_alpha_fac = BlendFactor::kSourceAlpha; 69 | BlendOp alpha_op = BlendOp::kAdd; 70 | BlendFactor dst_alpha_fac = BlendFactor::kOneMinusSourceAlpha; 71 | 72 | uint8_t write_mask = ColorMask::kAll; 73 | 74 | static constexpr glm::vec3 ApplyFactorColor(BlendFactor factor, 75 | glm::vec4 src, 76 | glm::vec4 dst) { 77 | switch (factor) { 78 | case BlendFactor::kZero: 79 | return glm::vec3{0.0}; 80 | case BlendFactor::kOne: 81 | return glm::vec3{1.0}; 82 | case BlendFactor::kSourceColor: 83 | return glm::vec3{src}; 84 | case BlendFactor::kOneMinusSourceColor: 85 | return glm::vec3{1.0} - glm::vec3{src}; 86 | case BlendFactor::kSourceAlpha: 87 | return glm::vec3{src.a}; 88 | case BlendFactor::kOneMinusSourceAlpha: 89 | return glm::vec3{1.0} - glm::vec3{src.a}; 90 | case BlendFactor::kDestinationColor: 91 | return glm::vec3{dst}; 92 | case BlendFactor::kOneMinusDestinationColor: 93 | return glm::vec3{1.0} - glm::vec3{dst}; 94 | case BlendFactor::kDestinationAlpha: 95 | return glm::vec3{dst.a}; 96 | case BlendFactor::kOneMinusDestinationAlpha: 97 | return glm::vec3{1.0} - glm::vec3{dst.a}; 98 | case BlendFactor::kSourceAlphaSaturated: 99 | return glm::min(glm::vec3{src.a}, glm::vec3{1} - glm::vec3{dst.a}); 100 | } 101 | return {}; 102 | } 103 | 104 | static constexpr ScalarF ApplyFactorAlpha(BlendFactor factor, 105 | glm::vec4 src, 106 | glm::vec4 dst) { 107 | switch (factor) { 108 | case BlendFactor::kZero: 109 | return 0.0; 110 | case BlendFactor::kOne: 111 | return 1.0; 112 | case BlendFactor::kSourceColor: 113 | return src.a; 114 | case BlendFactor::kOneMinusSourceColor: 115 | return 1.0 - src.a; 116 | case BlendFactor::kSourceAlpha: 117 | return src.a; 118 | case BlendFactor::kOneMinusSourceAlpha: 119 | return 1.0 - src.a; 120 | case BlendFactor::kDestinationColor: 121 | return dst.a; 122 | case BlendFactor::kOneMinusDestinationColor: 123 | return 1.0 - dst.a; 124 | case BlendFactor::kDestinationAlpha: 125 | return dst.a; 126 | case BlendFactor::kOneMinusDestinationAlpha: 127 | return 1.0 - dst.a; 128 | case BlendFactor::kSourceAlphaSaturated: 129 | return 1; 130 | } 131 | return 0; 132 | } 133 | 134 | template 135 | SFT_ALWAYS_INLINE static constexpr T ApplyOp(BlendOp op, 136 | const T& src, 137 | const T& dst) { 138 | switch (op) { 139 | case BlendOp::kAdd: 140 | return src + dst; 141 | case BlendOp::kSubtract: 142 | return src - dst; 143 | case BlendOp::kReverseSubtract: 144 | return dst - src; 145 | case BlendOp::kMin: 146 | return glm::min(src, dst); 147 | case BlendOp::kMax: 148 | return glm::max(src, dst); 149 | } 150 | return src + dst; 151 | } 152 | 153 | static constexpr glm::vec3 RGB(const glm::vec4& color) { 154 | return glm::vec3{color}; 155 | } 156 | 157 | SFT_ALWAYS_INLINE static constexpr glm::vec4 Masked(glm::vec4 src, 158 | glm::vec4 dst, 159 | uint8_t mask) { 160 | return glm::vec4{ 161 | mask & ColorMask::kRed ? src.r : dst.r, // 162 | mask & ColorMask::kGreen ? src.g : dst.g, // 163 | mask & ColorMask::kBlue ? src.b : dst.b, // 164 | mask & ColorMask::kAlpha ? src.a : dst.a, // 165 | }; 166 | } 167 | 168 | SFT_ALWAYS_INLINE constexpr glm::vec4 Blend(glm::vec4 src, 169 | glm::vec4 dst) const { 170 | if (!enabled) { 171 | return Masked(src, dst, write_mask); 172 | } 173 | auto color = 174 | ApplyOp(color_op, // 175 | ApplyFactorColor(src_color_fac, src, dst) * RGB(src), // 176 | ApplyFactorColor(dst_color_fac, src, dst) * RGB(dst) // 177 | ); 178 | auto alpha = ApplyOp(alpha_op, // 179 | ApplyFactorAlpha(src_color_fac, src, dst) * src.a, // 180 | ApplyFactorAlpha(dst_color_fac, src, dst) * dst.a // 181 | ); 182 | return Masked(glm::vec4{color.x, color.y, color.z, alpha}, dst, write_mask); 183 | } 184 | }; 185 | 186 | // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators 187 | enum class BlendMode { 188 | kClear, 189 | kCopy, 190 | kDestination, 191 | kSourceOver, 192 | kDestinationOver, 193 | kSourceIn, 194 | kDestinationIn, 195 | kSourceOut, 196 | kDestinationOut, 197 | kSourceAtop, 198 | kDestinationAtop, 199 | kXOR, 200 | }; 201 | 202 | constexpr BlendDescriptor BlendDescriptorForMode(BlendMode mode) { 203 | BlendDescriptor desc; 204 | desc.enabled = true; 205 | switch (mode) { 206 | case BlendMode::kClear: 207 | desc.dst_alpha_fac = BlendFactor::kZero; 208 | desc.dst_color_fac = BlendFactor::kZero; 209 | desc.src_alpha_fac = BlendFactor::kZero; 210 | desc.src_color_fac = BlendFactor::kZero; 211 | break; 212 | case BlendMode::kCopy: 213 | desc.dst_alpha_fac = BlendFactor::kZero; 214 | desc.dst_color_fac = BlendFactor::kZero; 215 | desc.src_alpha_fac = BlendFactor::kOne; 216 | desc.src_color_fac = BlendFactor::kOne; 217 | break; 218 | case BlendMode::kDestination: 219 | desc.dst_alpha_fac = BlendFactor::kDestinationAlpha; 220 | desc.dst_color_fac = BlendFactor::kOne; 221 | desc.src_alpha_fac = BlendFactor::kZero; 222 | desc.src_color_fac = BlendFactor::kZero; 223 | break; 224 | case BlendMode::kSourceOver: 225 | desc.dst_alpha_fac = BlendFactor::kOneMinusSourceAlpha; 226 | desc.dst_color_fac = BlendFactor::kOneMinusSourceAlpha; 227 | desc.src_alpha_fac = BlendFactor::kOne; 228 | desc.src_color_fac = BlendFactor::kOne; 229 | break; 230 | case BlendMode::kDestinationOver: 231 | desc.dst_alpha_fac = BlendFactor::kDestinationAlpha; 232 | desc.dst_color_fac = BlendFactor::kOne; 233 | desc.src_alpha_fac = BlendFactor::kOneMinusDestinationAlpha; 234 | desc.src_color_fac = BlendFactor::kOneMinusDestinationAlpha; 235 | break; 236 | case BlendMode::kSourceIn: 237 | desc.dst_alpha_fac = BlendFactor::kZero; 238 | desc.dst_color_fac = BlendFactor::kZero; 239 | desc.src_alpha_fac = BlendFactor::kDestinationAlpha; 240 | desc.src_color_fac = BlendFactor::kDestinationAlpha; 241 | break; 242 | case BlendMode::kDestinationIn: 243 | desc.dst_alpha_fac = BlendFactor::kSourceAlpha; 244 | desc.dst_color_fac = BlendFactor::kSourceAlpha; 245 | desc.src_alpha_fac = BlendFactor::kZero; 246 | desc.src_color_fac = BlendFactor::kZero; 247 | break; 248 | case BlendMode::kSourceOut: 249 | desc.dst_alpha_fac = BlendFactor::kZero; 250 | desc.dst_color_fac = BlendFactor::kZero; 251 | desc.src_alpha_fac = BlendFactor::kOneMinusDestinationAlpha; 252 | desc.src_color_fac = BlendFactor::kOneMinusDestinationAlpha; 253 | break; 254 | case BlendMode::kDestinationOut: 255 | desc.dst_alpha_fac = BlendFactor::kOneMinusSourceAlpha; 256 | desc.dst_color_fac = BlendFactor::kOneMinusSourceAlpha; 257 | desc.src_alpha_fac = BlendFactor::kZero; 258 | desc.src_color_fac = BlendFactor::kZero; 259 | break; 260 | case BlendMode::kSourceAtop: 261 | desc.dst_alpha_fac = BlendFactor::kOneMinusSourceAlpha; 262 | desc.dst_color_fac = BlendFactor::kOneMinusSourceAlpha; 263 | desc.src_alpha_fac = BlendFactor::kDestinationAlpha; 264 | desc.src_color_fac = BlendFactor::kDestinationAlpha; 265 | break; 266 | case BlendMode::kDestinationAtop: 267 | desc.dst_alpha_fac = BlendFactor::kSourceAlpha; 268 | desc.dst_color_fac = BlendFactor::kSourceAlpha; 269 | desc.src_alpha_fac = BlendFactor::kOneMinusDestinationAlpha; 270 | desc.src_color_fac = BlendFactor::kOneMinusDestinationAlpha; 271 | break; 272 | case BlendMode::kXOR: 273 | desc.dst_alpha_fac = BlendFactor::kOneMinusSourceAlpha; 274 | desc.dst_color_fac = BlendFactor::kOneMinusSourceAlpha; 275 | desc.src_alpha_fac = BlendFactor::kOneMinusDestinationAlpha; 276 | desc.src_color_fac = BlendFactor::kOneMinusDestinationAlpha; 277 | break; 278 | } 279 | return desc; 280 | } 281 | 282 | } // namespace sft 283 | -------------------------------------------------------------------------------- /src/rasterizer/image.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "image.h" 7 | 8 | #define STB_IMAGE_IMPLEMENTATION 9 | #include "stb_image.h" 10 | 11 | #include 12 | 13 | namespace sft { 14 | 15 | std::shared_ptr Image::Create(const char* file_path) { 16 | return std::shared_ptr(new Image(file_path)); 17 | } 18 | 19 | std::shared_ptr Image::Create(std::shared_ptr mapping, 20 | glm::ivec2 size) { 21 | return std::shared_ptr(new Image(std::move(mapping), size)); 22 | } 23 | 24 | Image::Image(const char* path) { 25 | int width = 0; 26 | int height = 0; 27 | int channels_in_file = 0; 28 | 29 | auto* decoded = ::stbi_load(path, &width, &height, &channels_in_file, 4); 30 | 31 | if (!decoded) { 32 | std::cout << "Could not decode image at path: " << path; 33 | return; 34 | } 35 | 36 | mapping_ = std::make_shared( 37 | decoded, width * height * 4, [decoded]() { ::stbi_image_free(decoded); }); 38 | size_ = {width, height}; 39 | } 40 | 41 | Image::Image(std::shared_ptr mapping, glm::ivec2 size) 42 | : mapping_(std::move(mapping)), size_(size) {} 43 | 44 | Image::~Image() = default; 45 | 46 | constexpr ScalarF SamplerLocation(ScalarF location, WrapMode mode) { 47 | // Section 3.7.6 "Texture Wrap Modes" 48 | // https://registry.khronos.org/OpenGL/specs/es/2.0/es_full_spec_2.0.pdf 49 | switch (mode) { 50 | case WrapMode::kClamp: 51 | return glm::clamp(location, 0.0f, 1.0f); 52 | case WrapMode::kRepeat: 53 | return glm::fract(location); 54 | case WrapMode::kMirror: { 55 | const auto is_even = static_cast(glm::floor(location)) % 2 == 0; 56 | const auto fract = glm::fract(location); 57 | return is_even ? fract : 1.0 - fract; 58 | } 59 | } 60 | return 0.0; 61 | } 62 | 63 | glm::vec4 Image::Sample(glm::vec2 pos) const { 64 | if (size_.x * size_.y <= 0) { 65 | return kColorBlack; 66 | } 67 | 68 | return SampleUV({SamplerLocation(pos.x, sampler_.wrap_mode_s), 69 | SamplerLocation(pos.y, sampler_.wrap_mode_t)}); 70 | } 71 | 72 | glm::vec4 Image::SampleUnitNearest(glm::vec2 uv) const { 73 | return SampleXY({ 74 | glm::clamp(uv.x * size_.x, 0, size_.x - 1), 75 | glm::clamp(uv.y * size_.y, 0, size_.y - 1), 76 | }); 77 | } 78 | 79 | glm::vec4 Image::SampleUnitLinear(glm::vec2 uv) const { 80 | ScalarF x = uv.x * size_.x; 81 | ScalarF y = uv.y * size_.y; 82 | 83 | ScalarF i0 = glm::floor(x - 0.5f); 84 | ScalarF j0 = glm::floor(y - 0.5f); 85 | 86 | if (sampler_.wrap_mode_s == WrapMode::kRepeat) { 87 | i0 = glm::mod(i0, static_cast(size_.x)); 88 | } 89 | 90 | if (sampler_.wrap_mode_t == WrapMode::kRepeat) { 91 | j0 = glm::mod(j0, static_cast(size_.y)); 92 | } 93 | 94 | ScalarF i1 = i0 + 1.0f; 95 | ScalarF j1 = j0 + 1.0f; 96 | 97 | if (sampler_.wrap_mode_s == WrapMode::kRepeat) { 98 | i1 = glm::mod(i1, static_cast(size_.x)); 99 | } 100 | 101 | if (sampler_.wrap_mode_t == WrapMode::kRepeat) { 102 | j1 = glm::mod(j1, static_cast(size_.y)); 103 | } 104 | 105 | ScalarF a = glm::fract(x - 0.5f); 106 | ScalarF b = glm::fract(y - 0.5f); 107 | 108 | glm::vec4 ti0j0 = SampleXY({i0, j0}); 109 | glm::vec4 ti1j0 = SampleXY({i1, j0}); 110 | glm::vec4 ti0j1 = SampleXY({i0, j1}); 111 | glm::vec4 ti1j1 = SampleXY({i1, j1}); 112 | 113 | return ((1 - a) * (1 - b) * ti0j0) + // 114 | (a * (1 - b) * ti1j0) + // 115 | ((1 - a) * b * ti0j1) + // 116 | (a * b * ti1j1) // 117 | ; 118 | } 119 | 120 | // From 3.7.7 Texture Minification 121 | // https://registry.khronos.org/OpenGL/specs/es/2.0/es_full_spec_2.0.pdf 122 | glm::vec4 Image::SampleUV(glm::vec2 uv) const { 123 | switch (sampler_.min_mag_filter) { 124 | case Filter::kNearest: 125 | return SampleUnitNearest(uv); 126 | case Filter::kLinear: 127 | return SampleUnitLinear(uv); 128 | } 129 | return kColorBlack; 130 | } 131 | 132 | const uint8_t* Image::GetBuffer() const { 133 | return mapping_ ? mapping_->GetBuffer() : nullptr; 134 | } 135 | 136 | glm::vec4 Image::SampleXY(glm::ivec2 xy) const { 137 | xy = glm::clamp(xy, {0, 0}, {size_.x - 1, size_.y - 1}); 138 | auto offset = size_.x * xy.y + xy.x; 139 | const Color* icolor = reinterpret_cast(GetBuffer()) + offset; 140 | return *icolor; 141 | } 142 | 143 | void Image::SetSampler(Sampler sampler) { 144 | sampler_ = std::move(sampler); 145 | } 146 | 147 | const Sampler& Image::GetSampler() const { 148 | return sampler_; 149 | } 150 | 151 | glm::ivec2 Image::GetSize() const { 152 | return size_; 153 | } 154 | 155 | bool Image::IsValid() const { 156 | return is_valid_; 157 | } 158 | 159 | } // namespace sft 160 | -------------------------------------------------------------------------------- /src/rasterizer/image.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include "geometry.h" 11 | #include "macros.h" 12 | #include "mapping.h" 13 | #include "sampler.h" 14 | 15 | namespace sft { 16 | 17 | class Image final : public std::enable_shared_from_this { 18 | public: 19 | static std::shared_ptr Create(const char* file_path); 20 | 21 | static std::shared_ptr Create(std::shared_ptr mapping, 22 | glm::ivec2 size); 23 | 24 | ~Image(); 25 | 26 | void SetSampler(Sampler sampler); 27 | 28 | const Sampler& GetSampler() const; 29 | 30 | glm::ivec2 GetSize() const; 31 | 32 | bool IsValid() const; 33 | 34 | glm::vec4 Sample(glm::vec2 uv) const; 35 | 36 | private: 37 | std::shared_ptr mapping_; 38 | glm::ivec2 size_; 39 | Sampler sampler_; 40 | bool is_valid_; 41 | 42 | Image(const char* file_path); 43 | 44 | Image(std::shared_ptr mapping, glm::ivec2 size); 45 | 46 | glm::vec4 SampleUV(glm::vec2 uv) const; 47 | 48 | glm::vec4 SampleXY(glm::ivec2 xy) const; 49 | 50 | glm::vec4 SampleUnitNearest(glm::vec2 uv) const; 51 | 52 | glm::vec4 SampleUnitLinear(glm::vec2 uv) const; 53 | 54 | const uint8_t* GetBuffer() const; 55 | 56 | SFT_DISALLOW_COPY_AND_ASSIGN(Image); 57 | }; 58 | 59 | } // namespace sft 60 | -------------------------------------------------------------------------------- /src/rasterizer/invocation.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "invocation.h" 7 | 8 | #include "rasterizer.h" 9 | 10 | namespace sft {} // namespace sft 11 | -------------------------------------------------------------------------------- /src/rasterizer/invocation.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "geometry.h" 9 | #include "macros.h" 10 | #include "rasterizer.h" 11 | #include "tiler.h" 12 | 13 | namespace sft { 14 | 15 | class Rasterizer; 16 | struct VertexResources; 17 | 18 | struct VertexInvocation { 19 | template 20 | T LoadVertexData(size_t offset) const { 21 | return vtx_resources.LoadVertexData(vtx_index, offset); 22 | } 23 | 24 | template 25 | T LoadUniform(size_t struct_offset) const { 26 | return vtx_resources.resources->LoadUniform(struct_offset); 27 | } 28 | 29 | template 30 | void StoreVarying(const T& value, size_t struct_offset) const { 31 | frag_resources.StoreVarying(value, vtx_index, struct_offset); 32 | } 33 | 34 | private: 35 | friend Rasterizer; 36 | 37 | size_t vtx_index; 38 | const VertexResources& vtx_resources; 39 | const FragmentResources& frag_resources; 40 | 41 | VertexInvocation(const VertexResources& p_vtx_resources, 42 | const FragmentResources& p_frag_resources, 43 | size_t p_vertex_id) 44 | : vtx_index(p_vertex_id), 45 | vtx_resources(p_vtx_resources), 46 | frag_resources(p_frag_resources) {} 47 | }; 48 | 49 | struct FragmentInvocation { 50 | template 51 | T LoadVarying(size_t offset) const { 52 | return frag_resources.LoadVarying(barycentric_coordinates, // 53 | offset // 54 | ); 55 | } 56 | 57 | template 58 | T LoadUniform(size_t struct_offset) const { 59 | return frag_resources.resources->LoadUniform(struct_offset); 60 | } 61 | 62 | const Image& LoadImage(size_t location) const { 63 | return frag_resources.LoadImage(location); 64 | } 65 | 66 | private: 67 | friend Rasterizer; 68 | 69 | glm::vec3 barycentric_coordinates; 70 | const FragmentResources& frag_resources; 71 | 72 | FragmentInvocation(glm::vec3 p_barycentric_coordinates, 73 | const FragmentResources& p_resources) 74 | : barycentric_coordinates(p_barycentric_coordinates), 75 | frag_resources(p_resources) {} 76 | }; 77 | 78 | } // namespace sft 79 | -------------------------------------------------------------------------------- /src/rasterizer/pipeline.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "pipeline.h" 7 | 8 | namespace sft {} 9 | -------------------------------------------------------------------------------- /src/rasterizer/pipeline.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | 11 | #include "attachment.h" 12 | #include "geometry.h" 13 | #include "shader.h" 14 | #include "vertex_descriptor.h" 15 | 16 | namespace sft { 17 | 18 | enum class CullFace { 19 | kFront, 20 | kBack, 21 | }; 22 | 23 | enum class Winding { 24 | kClockwise, 25 | kCounterClockwise, 26 | }; 27 | 28 | struct Pipeline { 29 | ColorAttachmentDescriptor color_desc; 30 | DepthAttachmentDescriptor depth_desc; 31 | StencilAttachmentDescriptor stencil_desc; 32 | std::optional viewport; 33 | std::shared_ptr shader; 34 | VertexDescriptor vertex_descriptor; 35 | Winding winding = Winding::kClockwise; 36 | std::optional cull_face; 37 | std::optional scissor; 38 | }; 39 | 40 | } // namespace sft 41 | -------------------------------------------------------------------------------- /src/rasterizer/rasterizer.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "rasterizer.h" 7 | 8 | #include 9 | 10 | #include "image.h" 11 | #include "invocation.h" 12 | #include "macros.h" 13 | #include "mapping.h" 14 | 15 | namespace sft { 16 | 17 | Rasterizer::Rasterizer(glm::ivec2 size, SampleCount sample_count) 18 | : pass_(size, sample_count) { 19 | size_ = pass_.GetSize(); 20 | } 21 | 22 | Rasterizer::~Rasterizer() = default; 23 | 24 | glm::ivec2 Rasterizer::GetSize() const { 25 | return pass_.GetSize(); 26 | } 27 | 28 | constexpr bool IsOOB(glm::ivec2 pos, glm::ivec2 size) { 29 | return pos.x < 0 || pos.y < 0 || pos.x >= size.x || pos.y >= size.y; 30 | } 31 | 32 | bool Rasterizer::FragmentPassesDepthTest(const Pipeline& pipeline, 33 | glm::ivec2 pos, 34 | ScalarF new_value, 35 | size_t sample) const { 36 | if (IsOOB(pos, size_)) { 37 | return false; 38 | } 39 | 40 | if (!pipeline.depth_desc.depth_test_enabled) { 41 | return true; 42 | } 43 | 44 | const auto current_value = *pass_.depth.texture->Get(pos, sample); 45 | 46 | return CompareFunctionPasses(pipeline.depth_desc.depth_compare, // 47 | new_value, // 48 | current_value // 49 | ); 50 | } 51 | 52 | bool Rasterizer::UpdateAndCheckFragmentPassesStencilTest( 53 | const Pipeline& pipeline, 54 | glm::ivec2 pos, 55 | bool depth_test_passes, 56 | uint32_t reference_value, 57 | size_t sample) { 58 | if (IsOOB(pos, size_)) { 59 | return false; 60 | } 61 | 62 | if (!pipeline.stencil_desc.stencil_test_enabled) { 63 | return true; 64 | } 65 | 66 | const auto read_mask = pipeline.stencil_desc.read_mask; 67 | const auto write_mask = pipeline.stencil_desc.write_mask; 68 | 69 | const auto current_value = *pass_.stencil.texture->Get(pos, sample); 70 | 71 | const auto stencil_test_passes = 72 | CompareFunctionPasses(pipeline.stencil_desc.stencil_compare, // 73 | read_mask & current_value, // 74 | read_mask & reference_value // 75 | ); 76 | 77 | //------------------------------------------------------------------------ 78 | // Determine the new stencil value. 79 | //------------------------------------------------------------------------ 80 | const auto stencil_op = 81 | pipeline.stencil_desc.SelectOperation(depth_test_passes, // 82 | stencil_test_passes // 83 | ); 84 | 85 | const auto new_stencil_value = 86 | StencilOperationPerform( 87 | stencil_op, // selected stencil operation 88 | read_mask & current_value, // current stencil value 89 | read_mask & reference_value // stencil reference value 90 | ) & 91 | write_mask; 92 | 93 | //------------------------------------------------------------------------ 94 | // Update the stencil value. 95 | //------------------------------------------------------------------------ 96 | pass_.stencil.texture->Set(new_stencil_value, pos, sample); 97 | 98 | return stencil_test_passes; 99 | } 100 | 101 | void Rasterizer::UpdateColor(const ColorAttachmentDescriptor& color_desc, 102 | const glm::ivec2& pos, 103 | const Color& src, 104 | size_t sample) { 105 | if (IsOOB(pos, size_)) { 106 | return; 107 | } 108 | if (color_desc.blend.enabled) { 109 | auto dst = *pass_.color.texture->Get(pos, sample); 110 | auto color = color_desc.blend.Blend(src, dst); 111 | pass_.color.texture->Set(color, pos, sample); 112 | } else { 113 | pass_.color.texture->Set(src, pos, sample); 114 | } 115 | } 116 | 117 | void Rasterizer::UpdateDepth(const DepthAttachmentDescriptor& depth_desc, 118 | const glm::ivec2& pos, 119 | ScalarF depth, 120 | size_t sample) { 121 | if (IsOOB(pos, size_)) { 122 | return; 123 | } 124 | if (!depth_desc.depth_test_enabled) { 125 | return; 126 | } 127 | if (!depth_desc.depth_write_enabled) { 128 | return; 129 | } 130 | pass_.depth.texture->Set(depth, pos, sample); 131 | } 132 | 133 | void Rasterizer::Clear(Color color) { 134 | pass_.color.clear_color = color; 135 | pass_.Load(); 136 | metrics_.area = pass_.GetSize(); 137 | tiler_.Reset(); 138 | } 139 | 140 | constexpr glm::vec3 ToNDC(const glm::vec4& clip) { 141 | auto ndc = glm::vec3{clip}; 142 | ndc /= clip.w; 143 | return ndc; 144 | } 145 | 146 | constexpr glm::vec2 ToTexelPos(const glm::vec3& ndc, 147 | const glm::vec2& viewport) { 148 | return { 149 | (viewport.x / 2.0f) * (ndc.x + 1.0f), // 150 | (viewport.y / 2.0f) * (ndc.y + 1.0f), // 151 | }; 152 | } 153 | 154 | constexpr Rect GetBoundingBox(glm::ivec2 p1, glm::ivec2 p2, glm::ivec2 p3) { 155 | const auto min = 156 | glm::vec2{std::min({p1.x, p2.x, p3.x}), std::min({p1.y, p2.y, p3.y})}; 157 | const auto max = 158 | glm::vec2{std::max({p1.x, p2.x, p3.x}), std::max({p1.y, p2.y, p3.y})}; 159 | return Rect{{min.x, min.y}, {max.x - min.x, max.y - min.y}}; 160 | } 161 | 162 | constexpr glm::vec3 GetBaryCentricCoordinates(glm::vec2 p, 163 | glm::vec2 a, 164 | glm::vec2 b, 165 | glm::vec2 c) { 166 | glm::vec2 ab = b - a, ac = c - a, ap = p - a; 167 | float one_over_den = 1.0f / (ab.x * ac.y - ab.y * ac.x); 168 | float s = (ac.y * ap.x - ac.x * ap.y) * one_over_den; 169 | float t = (ab.x * ap.y - ab.y * ap.x) * one_over_den; 170 | return {1.0f - s - t, s, t}; 171 | } 172 | 173 | constexpr bool ShouldCullFace(CullFace face, 174 | Winding winding, 175 | glm::vec3 a, 176 | glm::vec3 b, 177 | glm::vec3 c) { 178 | auto dir = glm::cross(b - a, c - a).z; 179 | const bool is_front = face == CullFace::kFront; 180 | const bool is_cw = winding == Winding::kClockwise; 181 | if (!is_front) { 182 | dir = -dir; 183 | } 184 | if (!is_cw) { 185 | dir = -dir; 186 | } 187 | return dir < 0; 188 | } 189 | 190 | //------------------------------------------------------------------------------ 191 | /// @brief Return if a point `p` is on the left, right or on the line from 192 | /// `v0` to `v1`. 193 | /// 194 | /// @param[in] v0 The point v0. 195 | /// @param[in] v1 The point v1. 196 | /// @param[in] p The point p. 197 | /// 198 | /// @return Less than 0 if on the left, greater than zero if one the right 199 | /// and zero if on the line. 200 | /// 201 | constexpr ScalarF EdgeFunction(const glm::vec2& v0, 202 | const glm::vec2& v1, 203 | const glm::vec2& p) { 204 | return (p.x - v0.x) * (v1.y - v0.y) - (p.y - v0.y) * (v1.x - v0.x); 205 | } 206 | 207 | static bool IsTopLeftEdge(const glm::vec2& edge) { 208 | // Top edges are flat (y == 0) and go right (x == 0). 209 | const auto is_top = 210 | glm::epsilonEqual(edge.y, 0.0f, kEpsilon) && edge.x > 0.0f; 211 | 212 | // Left edges are ones that go up. 213 | const auto is_left = edge.y > 0.0f; 214 | 215 | return is_top || is_left; 216 | } 217 | 218 | static bool PointInside(const glm::vec2& a, 219 | const glm::vec2& b, 220 | const glm::vec2& c, 221 | const glm::vec2& p) { 222 | const auto edge_ab = EdgeFunction(a, b, p); 223 | const auto edge_bc = EdgeFunction(b, c, p); 224 | const auto edge_ca = EdgeFunction(c, a, p); 225 | 226 | // The point is clearly not in the triangle or an edge. 227 | if (edge_ab < -kEpsilon || edge_bc < -kEpsilon || edge_ca < -kEpsilon) { 228 | return false; 229 | } 230 | 231 | // Check if the triangle is on the edge. If it is, we need to apply the 232 | // Top-Left rule. 233 | // https://learn.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-rasterizer-stage-rules 234 | const auto on_ab = glm::epsilonEqual(edge_ab, 0.0f, kEpsilon); 235 | const auto on_bc = glm::epsilonEqual(edge_bc, 0.0f, kEpsilon); 236 | const auto on_ca = glm::epsilonEqual(edge_ca, 0.0f, kEpsilon); 237 | 238 | const auto ab = b - a; 239 | const auto bc = c - b; 240 | const auto ca = a - c; 241 | 242 | if (on_ab && !IsTopLeftEdge(ab)) { 243 | return false; 244 | } 245 | 246 | if (on_bc && !IsTopLeftEdge(bc)) { 247 | return false; 248 | } 249 | 250 | if (on_ca && !IsTopLeftEdge(ca)) { 251 | return false; 252 | } 253 | 254 | return true; 255 | } 256 | 257 | void Rasterizer::ShadeFragments(const FragmentResources& tiler_data, 258 | const Rect& tile) { 259 | auto box = tiler_data.box.Intersection(tile); 260 | if (!box.has_value()) { 261 | return; 262 | } 263 | const auto sample_count = pass_.color.texture->GetSampleCount(); 264 | const auto& pipeline = tiler_data.pipeline; 265 | auto viewport = pipeline->viewport.value_or(size_); 266 | const auto frag_p1 = ToTexelPos(tiler_data.ndc[0], viewport); 267 | const auto frag_p2 = ToTexelPos(tiler_data.ndc[1], viewport); 268 | const auto frag_p3 = ToTexelPos(tiler_data.ndc[2], viewport); 269 | //---------------------------------------------------------------------------- 270 | // Shade fragments. 271 | //---------------------------------------------------------------------------- 272 | for (auto y = box->origin.y; y <= box->origin.y + box->size.height; y++) { 273 | for (auto x = box->origin.x; x <= box->origin.x + box->size.width; x++) { 274 | const auto pixel = glm::vec2{x, y}; 275 | uint32_t samples_found = 0; 276 | 277 | for (size_t sample = 0; sample < GetSampleCount(sample_count); sample++) { 278 | const auto frag = pixel + GetSampleLocation(sample_count, sample); 279 | 280 | if (!PointInside(frag_p1, frag_p2, frag_p3, frag)) { 281 | continue; 282 | } 283 | 284 | //---------------------------------------------------------------------- 285 | // Perform the depth test. 286 | //---------------------------------------------------------------------- 287 | const auto bary = 288 | GetBaryCentricCoordinates(frag, frag_p1, frag_p2, frag_p3); 289 | const auto depth = BarycentricInterpolation(tiler_data.ndc[0], // 290 | tiler_data.ndc[1], // 291 | tiler_data.ndc[2], // 292 | bary // 293 | ) 294 | .z; 295 | const auto depth_test_passes = 296 | FragmentPassesDepthTest(*pipeline, frag, depth, sample); 297 | 298 | //---------------------------------------------------------------------- 299 | // Perform the stencil test. 300 | //---------------------------------------------------------------------- 301 | const auto stencil_test_passes = 302 | UpdateAndCheckFragmentPassesStencilTest( 303 | *pipeline, // 304 | frag, // 305 | depth_test_passes, // 306 | tiler_data.stencil_reference, // 307 | sample // 308 | ); 309 | 310 | //---------------------------------------------------------------------- 311 | // If either the depth stencil tests have failed, short circuit fragment 312 | // processing. 313 | //---------------------------------------------------------------------- 314 | if (!stencil_test_passes || !depth_test_passes) { 315 | metrics_.early_fragment_test++; 316 | continue; 317 | } 318 | 319 | //---------------------------------------------------------------------- 320 | // Update the depth values. 321 | //---------------------------------------------------------------------- 322 | UpdateDepth(pipeline->depth_desc, frag, depth, sample); 323 | 324 | //---------------------------------------------------------------------- 325 | // This sample location needs a color value. 326 | //---------------------------------------------------------------------- 327 | samples_found |= (1 << sample); 328 | } 329 | 330 | if (samples_found == 0) { 331 | continue; 332 | } 333 | 334 | //------------------------------------------------------------------------ 335 | // Shade the fragment. But just once for all samples. 336 | //------------------------------------------------------------------------ 337 | const auto frag = pixel + kSampleMidpoint; 338 | const auto bary = 339 | GetBaryCentricCoordinates(frag, frag_p1, frag_p2, frag_p3); 340 | const auto color = 341 | Color{pipeline->shader->ProcessFragment({bary, tiler_data})}; 342 | metrics_.fragment_invocations++; 343 | 344 | //------------------------------------------------------------------------ 345 | // Blend in the color for found samples. 346 | //------------------------------------------------------------------------ 347 | for (size_t sample = 0; sample < GetSampleCount(sample_count); sample++) { 348 | if (samples_found & (1 << sample)) { 349 | const auto frag = pixel + GetSampleLocation(sample_count, sample); 350 | UpdateColor(pipeline->color_desc, frag, color, sample); 351 | } 352 | } 353 | } 354 | } 355 | } 356 | 357 | void Rasterizer::DrawTriangle(const VertexResources& data) { 358 | metrics_.primitive_count++; 359 | 360 | auto tiler_data = FragmentResources{data.pipeline->shader->GetVaryingsSize()}; 361 | tiler_data.stencil_reference = data.stencil_reference; 362 | tiler_data.pipeline = data.pipeline; 363 | tiler_data.resources = data.resources; 364 | 365 | //---------------------------------------------------------------------------- 366 | // Invoke vertex shaders. The clip-space coordinates returned are specified by 367 | // homogenous 4D vectors. 368 | //---------------------------------------------------------------------------- 369 | VertexInvocation vertex_invocation(data, tiler_data, data.base_vertex_id); 370 | const auto clip_p1 = data.pipeline->shader->ProcessVertex(vertex_invocation); 371 | vertex_invocation.vtx_index++; 372 | const auto clip_p2 = data.pipeline->shader->ProcessVertex(vertex_invocation); 373 | vertex_invocation.vtx_index++; 374 | const auto clip_p3 = data.pipeline->shader->ProcessVertex(vertex_invocation); 375 | metrics_.vertex_invocations += 3; 376 | 377 | //---------------------------------------------------------------------------- 378 | // Convert clip space coordinates into NDC coordinates (divide by w). 379 | //---------------------------------------------------------------------------- 380 | const auto ndc_p1 = ToNDC(clip_p1); 381 | const auto ndc_p2 = ToNDC(clip_p2); 382 | const auto ndc_p3 = ToNDC(clip_p3); 383 | 384 | //---------------------------------------------------------------------------- 385 | // Cull faces. 386 | //---------------------------------------------------------------------------- 387 | if (data.pipeline->cull_face.has_value()) { 388 | if (ShouldCullFace(data.pipeline->cull_face.value(), // 389 | data.pipeline->winding, // 390 | ndc_p1, // 391 | ndc_p2, // 392 | ndc_p3 // 393 | )) { 394 | metrics_.face_culling++; 395 | return; 396 | } 397 | } 398 | 399 | //---------------------------------------------------------------------------- 400 | // Convert NDC points returned by the shader into screen-space. 401 | //---------------------------------------------------------------------------- 402 | auto viewport = data.pipeline->viewport.value_or(size_); 403 | const auto frag_p1 = ToTexelPos(ndc_p1, viewport); 404 | const auto frag_p2 = ToTexelPos(ndc_p2, viewport); 405 | const auto frag_p3 = ToTexelPos(ndc_p3, viewport); 406 | 407 | //---------------------------------------------------------------------------- 408 | // Find bounding box and apply scissor. 409 | //---------------------------------------------------------------------------- 410 | const auto bounding_box = GetBoundingBox(frag_p1, frag_p2, frag_p3); 411 | 412 | if (bounding_box.size.IsEmpty()) { 413 | metrics_.empty_primitive++; 414 | return; 415 | } 416 | 417 | auto scissor_box = 418 | bounding_box.Intersection(data.pipeline->scissor.value_or(Rect{size_})); 419 | 420 | if (!scissor_box.has_value()) { 421 | metrics_.scissor_culling++; 422 | return; 423 | } 424 | 425 | const auto& box = scissor_box.value(); 426 | 427 | //---------------------------------------------------------------------------- 428 | // Apply sample point culling. 429 | // From https://developer.arm.com/documentation/102540/0100/Primitive-culling 430 | //---------------------------------------------------------------------------- 431 | if (box.size.width < 2 && box.size.height < 2) { 432 | metrics_.sample_point_culling++; 433 | return; 434 | } 435 | 436 | metrics_.primitives_processed++; 437 | 438 | tiler_data.box = box; 439 | tiler_data.ndc[0] = ndc_p1; 440 | tiler_data.ndc[1] = ndc_p2; 441 | tiler_data.ndc[2] = ndc_p3; 442 | 443 | tiler_.AddData(std::move(tiler_data)); 444 | } 445 | 446 | void Rasterizer::ResetMetrics() { 447 | metrics_.Reset(); 448 | } 449 | 450 | const RasterizerMetrics& Rasterizer::GetMetrics() const { 451 | return metrics_; 452 | } 453 | 454 | template 455 | constexpr Color CreateDebugColor(T val, T min, T max) { 456 | const auto full_range = max - min; 457 | const auto range = val - min; 458 | if (full_range == 0) { 459 | return kColorRed; 460 | } 461 | const auto component = static_cast(range) / full_range; 462 | return Color::FromComponentsF(component, component, component, 1.0); 463 | } 464 | 465 | bool Rasterizer::Resize(glm::ivec2 size) { 466 | if (size_ == size) { 467 | return true; 468 | } 469 | if (!pass_.Resize(size)) { 470 | return false; 471 | } 472 | size_ = size; 473 | return true; 474 | } 475 | 476 | RenderPassAttachments& Rasterizer::GetRenderPassAttachments() { 477 | return pass_; 478 | } 479 | 480 | void Rasterizer::Draw(std::shared_ptr pipeline, 481 | const BufferView& vertex_buffer, 482 | const BufferView& index_buffer, 483 | Uniforms uniforms, 484 | size_t count, 485 | uint32_t stencil_reference) { 486 | metrics_.draw_count++; 487 | auto resources = std::make_shared(); 488 | resources->vertex = std::move(vertex_buffer); 489 | resources->index = std::move(index_buffer); 490 | resources->uniform = std::move(uniforms); 491 | VertexResources data(pipeline, // 492 | std::move(resources), // 493 | stencil_reference // 494 | ); 495 | const auto vtx_offset = pipeline->vertex_descriptor.offset; 496 | for (size_t i = 0; i < count; i += 3) { 497 | data.base_vertex_id = i; 498 | data.vtx[0] = data.LoadVertexData(i + 0, vtx_offset); 499 | data.vtx[1] = data.LoadVertexData(i + 1, vtx_offset); 500 | data.vtx[2] = data.LoadVertexData(i + 2, vtx_offset); 501 | DrawTriangle(data); 502 | } 503 | } 504 | 505 | void Rasterizer::Draw(std::shared_ptr pipeline, 506 | const BufferView& vertex_buffer, 507 | Uniforms uniforms, 508 | size_t count, 509 | uint32_t stencil_refernece) { 510 | return Draw(std::move(pipeline), vertex_buffer, {}, std::move(uniforms), 511 | count, stencil_refernece); 512 | } 513 | 514 | void Rasterizer::Finish() { 515 | tiler_.Dispatch(*this); 516 | } 517 | 518 | } // namespace sft 519 | -------------------------------------------------------------------------------- /src/rasterizer/rasterizer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "buffer.h" 15 | #include "buffer_view.h" 16 | #include "geometry.h" 17 | #include "pipeline.h" 18 | #include "rasterizer_metrics.h" 19 | #include "render_pass.h" 20 | #include "stage_resources.h" 21 | #include "texture.h" 22 | #include "tiler.h" 23 | 24 | namespace sft { 25 | 26 | class Image; 27 | 28 | class Rasterizer { 29 | public: 30 | Rasterizer(glm::ivec2 size, SampleCount sample_count); 31 | 32 | ~Rasterizer(); 33 | 34 | RenderPassAttachments& GetRenderPassAttachments(); 35 | 36 | glm::ivec2 GetSize() const; 37 | 38 | void Clear(Color color); 39 | 40 | void Finish(); 41 | 42 | void Draw(std::shared_ptr pipeline, 43 | const BufferView& vertex_buffer, 44 | Uniforms uniforms, 45 | size_t count, 46 | uint32_t stencil_refernence = 0); 47 | 48 | void Draw(std::shared_ptr pipeline, 49 | const BufferView& vertex_buffer, 50 | const BufferView& index_buffer, 51 | Uniforms uniforms, 52 | size_t count, 53 | uint32_t stencil_reference = 0); 54 | 55 | void ResetMetrics(); 56 | 57 | const RasterizerMetrics& GetMetrics() const; 58 | 59 | [[nodiscard]] bool Resize(glm::ivec2 size); 60 | 61 | [[nodiscard]] bool ResizeSamples(SampleCount count); 62 | 63 | void ShadeFragments(const FragmentResources& tiler_data, const Rect& tile); 64 | 65 | private: 66 | RenderPassAttachments pass_; 67 | glm::ivec2 size_; 68 | RasterizerMetrics metrics_; 69 | Tiler tiler_; 70 | 71 | bool FragmentPassesDepthTest(const Pipeline& pipeline, 72 | glm::ivec2 pos, 73 | ScalarF depth, 74 | size_t sample) const; 75 | 76 | bool UpdateAndCheckFragmentPassesStencilTest(const Pipeline& pipeline, 77 | glm::ivec2 pos, 78 | bool depth_test_passes, 79 | uint32_t reference_value, 80 | size_t sample); 81 | 82 | void UpdateColor(const ColorAttachmentDescriptor& color_desc, 83 | const glm::ivec2& pos, 84 | const Color& color, 85 | size_t sample); 86 | 87 | void UpdateDepth(const DepthAttachmentDescriptor& depth_desc, 88 | const glm::ivec2& pos, 89 | ScalarF depth, 90 | size_t sample); 91 | 92 | void DrawTriangle(const VertexResources& data); 93 | 94 | SFT_DISALLOW_COPY_AND_ASSIGN(Rasterizer); 95 | }; 96 | 97 | } // namespace sft 98 | -------------------------------------------------------------------------------- /src/rasterizer/rasterizer_metrics.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "rasterizer_metrics.h" 7 | 8 | namespace sft { 9 | 10 | // 11 | 12 | } // namespace sft 13 | -------------------------------------------------------------------------------- /src/rasterizer/rasterizer_metrics.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "geometry.h" 9 | #include "macros.h" 10 | 11 | #include 12 | 13 | namespace sft { 14 | 15 | struct RasterizerMetrics { 16 | glm::ivec2 area; 17 | size_t draw_count = 0; 18 | size_t primitive_count = 0; 19 | size_t primitives_processed = 0; 20 | size_t face_culling = 0; 21 | size_t empty_primitive = 0; 22 | size_t scissor_culling = 0; 23 | size_t sample_point_culling = 0; 24 | size_t early_fragment_test = 0; 25 | size_t vertex_invocations = 0; 26 | size_t fragment_invocations = 0; 27 | 28 | void Reset() { std::memset(this, 0, sizeof(RasterizerMetrics)); } 29 | }; 30 | 31 | } // namespace sft 32 | -------------------------------------------------------------------------------- /src/rasterizer/render_pass.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "render_pass.h" 7 | 8 | namespace sft {} // namespace sft 9 | -------------------------------------------------------------------------------- /src/rasterizer/render_pass.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "geometry.h" 9 | #include "macros.h" 10 | #include "marl/scheduler.h" 11 | #include "marl/waitgroup.h" 12 | #include "texture.h" 13 | 14 | namespace sft { 15 | 16 | enum class LoadAction { 17 | kDontCare, 18 | kLoad, 19 | kClear, 20 | }; 21 | 22 | enum class StoreAction { 23 | kDontCare, 24 | kStore, 25 | }; 26 | 27 | struct PassAttachment { 28 | LoadAction load_action = LoadAction::kClear; 29 | StoreAction store_action = StoreAction::kDontCare; 30 | 31 | virtual glm::ivec2 GetSize() const = 0; 32 | 33 | virtual bool IsValid() const = 0; 34 | 35 | virtual void Load() = 0; 36 | 37 | virtual void Store() = 0; 38 | }; 39 | 40 | struct ColorPassAttachment final : public PassAttachment { 41 | glm::vec4 clear_color = {0.0, 0.0, 0.0, 1.0}; 42 | std::shared_ptr> texture; 43 | std::shared_ptr> resolve; 44 | 45 | ColorPassAttachment(glm::ivec2 size, SampleCount sample_count) { 46 | texture = std::make_shared>(size, sample_count); 47 | if (sample_count != SampleCount::kOne) { 48 | resolve = std::make_shared>(size, SampleCount::kOne); 49 | } 50 | } 51 | 52 | glm::ivec2 GetSize() const override { return texture->GetSize(); } 53 | 54 | [[nodiscard]] bool Resize(const glm::ivec2& size) { 55 | if (!IsValid()) { 56 | return false; 57 | } 58 | if (size == GetSize()) { 59 | return true; 60 | } 61 | { 62 | if (resolve && !resolve->Resize(size)) { 63 | return false; 64 | } 65 | } 66 | return texture->Resize(size); 67 | } 68 | 69 | [[nodiscard]] bool SetSampleCount(SampleCount count) { 70 | if (!IsValid()) { 71 | return false; 72 | } 73 | return texture->UpdateSampleCount(count); 74 | } 75 | 76 | bool IsValid() const override { 77 | if (!texture) { 78 | return false; 79 | } 80 | if (texture->GetSampleCount() != SampleCount::kOne) { 81 | return resolve && resolve->GetSampleCount() == SampleCount::kOne; 82 | } 83 | return true; 84 | } 85 | 86 | void Load() override { 87 | switch (load_action) { 88 | case LoadAction::kDontCare: 89 | case LoadAction::kLoad: 90 | break; 91 | case LoadAction::kClear: 92 | texture->Clear(clear_color); 93 | break; 94 | } 95 | } 96 | 97 | void Store() override {} 98 | }; 99 | 100 | struct DepthPassAttachment : public PassAttachment { 101 | ScalarF clear_depth = 1.0; 102 | std::shared_ptr> texture; 103 | 104 | DepthPassAttachment(const glm::ivec2& size) { 105 | texture = std::make_shared>(size); 106 | } 107 | 108 | glm::ivec2 GetSize() const override { return texture->GetSize(); } 109 | 110 | bool IsValid() const override { return !!texture; } 111 | 112 | [[nodiscard]] bool Resize(const glm::ivec2& size) { 113 | if (!IsValid()) { 114 | return false; 115 | } 116 | if (size == GetSize()) { 117 | return true; 118 | } 119 | return texture->Resize(size); 120 | } 121 | 122 | [[nodiscard]] bool SetSampleCount(SampleCount count) { 123 | if (!IsValid()) { 124 | return false; 125 | } 126 | return texture->UpdateSampleCount(count); 127 | } 128 | 129 | void Load() override { 130 | switch (load_action) { 131 | case LoadAction::kDontCare: 132 | case LoadAction::kLoad: 133 | break; 134 | case LoadAction::kClear: 135 | texture->Clear(clear_depth); 136 | break; 137 | } 138 | } 139 | 140 | void Store() override {} 141 | }; 142 | 143 | struct StencilPassAttachment : public PassAttachment { 144 | uint32_t clear_stencil = 0; 145 | std::shared_ptr> texture; 146 | 147 | StencilPassAttachment(const glm::ivec2& size) { 148 | texture = std::make_shared>(size); 149 | } 150 | 151 | glm::ivec2 GetSize() const override { return texture->GetSize(); } 152 | 153 | bool IsValid() const override { return !!texture; } 154 | 155 | [[nodiscard]] bool Resize(const glm::ivec2& size) { 156 | if (!IsValid()) { 157 | return false; 158 | } 159 | if (size == GetSize()) { 160 | return true; 161 | } 162 | return texture->Resize(size); 163 | } 164 | 165 | [[nodiscard]] bool SetSampleCount(SampleCount count) { 166 | if (!IsValid()) { 167 | return false; 168 | } 169 | return texture->UpdateSampleCount(count); 170 | } 171 | 172 | void Load() override { 173 | switch (load_action) { 174 | case LoadAction::kDontCare: 175 | case LoadAction::kLoad: 176 | break; 177 | case LoadAction::kClear: 178 | texture->Clear(clear_stencil); 179 | break; 180 | } 181 | } 182 | 183 | void Store() override {} 184 | }; 185 | 186 | struct RenderPassAttachments { 187 | ColorPassAttachment color; 188 | DepthPassAttachment depth; 189 | StencilPassAttachment stencil; 190 | 191 | RenderPassAttachments(const glm::ivec2& size, SampleCount sample_count) 192 | : color(size, sample_count), depth(size), stencil(size) {} 193 | 194 | [[nodiscard]] bool Resize(const glm::ivec2& size) { 195 | return color.Resize(size) && depth.Resize(size) && stencil.Resize(size); 196 | } 197 | 198 | [[nodiscard]] bool SetSampleCount(SampleCount count) { 199 | return color.SetSampleCount(count) && depth.SetSampleCount(count) && 200 | stencil.SetSampleCount(count); 201 | } 202 | 203 | glm::ivec2 GetSize() const { 204 | if (!color.IsValid()) { 205 | return {}; 206 | } 207 | return color.GetSize(); 208 | } 209 | 210 | bool IsValid() const { 211 | if (!color.IsValid() || !depth.IsValid() || !stencil.IsValid()) { 212 | return false; 213 | } 214 | const auto texture_size = color.texture->GetSize(); 215 | const auto depth_size = depth.texture->GetSize(); 216 | const auto stencil_size = stencil.texture->GetSize(); 217 | return texture_size == depth_size && texture_size == stencil_size; 218 | } 219 | 220 | bool Load() { 221 | marl::WaitGroup wg(3); 222 | marl::schedule([wg, attachment = &color]() { 223 | attachment->Load(); 224 | wg.done(); 225 | }); 226 | marl::schedule([wg, attachment = &depth]() { 227 | attachment->Load(); 228 | wg.done(); 229 | }); 230 | marl::schedule([wg, attachment = &stencil]() { 231 | attachment->Load(); 232 | wg.done(); 233 | }); 234 | wg.wait(); 235 | return true; 236 | } 237 | 238 | bool Store() { 239 | color.Store(); 240 | depth.Store(); 241 | stencil.Store(); 242 | return true; 243 | } 244 | }; 245 | 246 | } // namespace sft 247 | -------------------------------------------------------------------------------- /src/rasterizer/sampler.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "sampler.h" 7 | 8 | namespace sft { 9 | 10 | // 11 | 12 | } // namespace sft 13 | -------------------------------------------------------------------------------- /src/rasterizer/sampler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "geometry.h" 9 | 10 | namespace sft { 11 | 12 | enum class WrapMode { 13 | kRepeat, 14 | kClamp, 15 | kMirror, 16 | }; 17 | 18 | enum class Filter { 19 | kNearest, 20 | kLinear, 21 | }; 22 | 23 | struct Sampler { 24 | WrapMode wrap_mode_s = WrapMode::kRepeat; 25 | WrapMode wrap_mode_t = WrapMode::kRepeat; 26 | Filter min_mag_filter = Filter::kNearest; 27 | }; 28 | 29 | } // namespace sft 30 | -------------------------------------------------------------------------------- /src/rasterizer/shader.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "shader.h" 7 | -------------------------------------------------------------------------------- /src/rasterizer/shader.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include "geometry.h" 11 | 12 | namespace sft { 13 | 14 | struct VertexInvocation; 15 | struct FragmentInvocation; 16 | 17 | #define VTX(struct_member) \ 18 | inv.LoadVertexData( \ 19 | offsetof(VertexData, struct_member)) 20 | 21 | #define VARYING_STORE(struct_member, value) \ 22 | inv.StoreVarying( \ 23 | value, offsetof(Varyings, struct_member)) 24 | #define VARYING_LOAD(member) \ 25 | inv.LoadVarying(offsetof(Varyings, member)) 26 | 27 | #define UNIFORM(member) \ 28 | inv.LoadUniform(offsetof(Uniforms, member)) 29 | 30 | #define FORWARD(vtx_member, var_member) \ 31 | VARYING_STORE(var_member, VTX(vtx_member)) 32 | 33 | class Shader { 34 | public: 35 | Shader() = default; 36 | 37 | virtual ~Shader() = default; 38 | 39 | virtual size_t GetVaryingsSize() const = 0; 40 | 41 | virtual glm::vec4 ProcessVertex(const VertexInvocation& inv) const = 0; 42 | 43 | virtual glm::vec4 ProcessFragment(const FragmentInvocation& inv) const = 0; 44 | }; 45 | 46 | } // namespace sft 47 | -------------------------------------------------------------------------------- /src/rasterizer/stage_resources.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "stage_resources.h" 7 | 8 | namespace sft {} // namespace sft 9 | -------------------------------------------------------------------------------- /src/rasterizer/stage_resources.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "buffer_view.h" 14 | #include "geometry.h" 15 | #include "macros.h" 16 | #include "pipeline.h" 17 | #include "uniforms.h" 18 | 19 | namespace sft { 20 | 21 | class Buffer; 22 | 23 | struct BufferView; 24 | 25 | struct DispatchResources { 26 | BufferView vertex; 27 | BufferView index; 28 | Uniforms uniform; 29 | 30 | template 31 | T LoadUniform(size_t offset) const { 32 | T result = {}; 33 | memcpy(&result, uniform.buffer.GetData() + offset, sizeof(T)); 34 | return result; 35 | } 36 | }; 37 | 38 | struct VertexResources { 39 | std::array vtx; 40 | size_t base_vertex_id = 0; 41 | std::shared_ptr pipeline; 42 | std::shared_ptr resources; 43 | const uint32_t stencil_reference; 44 | 45 | VertexResources(std::shared_ptr p_pipeline, 46 | std::shared_ptr p_resources, 47 | uint32_t p_stencil_reference) 48 | : pipeline(std::move(p_pipeline)), 49 | resources(std::move(p_resources)), 50 | stencil_reference(p_stencil_reference) {} 51 | 52 | size_t LoadVertexIndex(size_t index) const { 53 | if (!resources->index) { 54 | return index; 55 | } 56 | switch (pipeline->vertex_descriptor.index_type) { 57 | case IndexType::kUInt32: { 58 | auto index_ptr = 59 | reinterpret_cast(resources->index.GetData()) + 60 | index; 61 | return *index_ptr; 62 | } 63 | case IndexType::kUInt16: { 64 | auto index_ptr = 65 | reinterpret_cast(resources->index.GetData()) + 66 | index; 67 | return *index_ptr; 68 | } 69 | } 70 | return 0u; 71 | } 72 | 73 | const uint8_t* LoadVertexDataPtr(size_t index, size_t offset) const { 74 | const auto* vtx_ptr = resources->vertex.GetData() + offset; 75 | vtx_ptr += LoadVertexIndex(index) * pipeline->vertex_descriptor.stride; 76 | return vtx_ptr; 77 | } 78 | 79 | template 80 | T LoadVertexData(size_t index, size_t offset) const { 81 | T result; 82 | std::memmove(&result, LoadVertexDataPtr(index, offset), sizeof(T)); 83 | return result; 84 | } 85 | 86 | glm::vec3 LoadVertex(size_t index, size_t offset) { 87 | switch (pipeline->vertex_descriptor.vertex_format) { 88 | case VertexFormat::kFloat2: 89 | return {LoadVertexData(index, offset), 0.0}; 90 | case VertexFormat::kFloat3: 91 | return LoadVertexData(index, offset); 92 | } 93 | } 94 | }; 95 | 96 | struct FragmentResources { 97 | Rect box; 98 | glm::vec3 ndc[3]; 99 | std::shared_ptr pipeline; 100 | std::shared_ptr resources; 101 | uint32_t stencil_reference = 0; 102 | std::vector varyings; 103 | 104 | explicit FragmentResources(size_t varyings_stride) { 105 | varyings.resize(varyings_stride * 3u); 106 | } 107 | 108 | size_t GetVaryingsStride() const { return varyings.size() / 3u; } 109 | 110 | const Image& LoadImage(size_t location) const { 111 | return *resources->uniform.images.at(location); 112 | } 113 | 114 | template 115 | void StoreVarying(const T& val, 116 | size_t triangle_index, 117 | size_t struct_offset) const { 118 | auto varyings_offset = 119 | struct_offset + GetVaryingsStride() * (triangle_index % 3); 120 | auto ptr = const_cast(varyings.data()) + varyings_offset; 121 | memcpy(ptr, &val, sizeof(T)); 122 | } 123 | 124 | template 125 | T LoadVarying(const glm::vec3& barycentric_coordinates, 126 | size_t struct_offset) const { 127 | const auto stride = GetVaryingsStride(); 128 | auto ptr = varyings.data() + struct_offset; 129 | T p1, p2, p3; 130 | memcpy(&p1, ptr, sizeof(p1)); 131 | ptr += stride; 132 | memcpy(&p2, ptr, sizeof(p2)); 133 | ptr += stride; 134 | memcpy(&p3, ptr, sizeof(p3)); 135 | return BarycentricInterpolation(p1, p2, p3, barycentric_coordinates); 136 | } 137 | }; 138 | 139 | } // namespace sft 140 | -------------------------------------------------------------------------------- /src/rasterizer/texture.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "texture.h" 7 | 8 | namespace sft {} 9 | -------------------------------------------------------------------------------- /src/rasterizer/texture.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "geometry.h" 15 | #include "image.h" 16 | #include "macros.h" 17 | #include "marl/scheduler.h" 18 | #include "marl/waitgroup.h" 19 | 20 | namespace sft { 21 | 22 | enum class SampleCount : uint8_t { 23 | kOne = 1, 24 | kTwo = 2, 25 | kFour = 4, 26 | kEight = 8, 27 | kSixteen = 16, 28 | }; 29 | 30 | constexpr size_t GetSampleCount(SampleCount count) { 31 | return static_cast(count); 32 | } 33 | 34 | constexpr std::array kSampleLocationsOne = { 35 | glm::vec2{0.5f, 0.5f}}; 36 | 37 | constexpr std::array kSampleLocationsTwo = { 38 | glm::vec2{0.75f, 0.75f}, 39 | glm::vec2{0.25f, 0.25f}, 40 | }; 41 | 42 | constexpr std::array kSampleLocationsFour = { 43 | glm::vec2{0.375f, 0.125f}, 44 | glm::vec2{0.875f, 0.375f}, 45 | glm::vec2{0.125f, 0.625f}, 46 | glm::vec2{0.625f, 0.875f}, 47 | }; 48 | 49 | constexpr std::array kSampleLocationsEight = { 50 | glm::vec2{0.5625, 0.3125}, glm::vec2{0.4375, 0.6875}, 51 | glm::vec2{0.8125, 0.5625}, glm::vec2{0.3125, 0.1875}, 52 | glm::vec2{0.1875, 0.8125}, glm::vec2{0.0625, 0.4375}, 53 | glm::vec2{0.6875, 0.9375}, glm::vec2{0.9375, 0.0625}, 54 | }; 55 | 56 | constexpr std::array kSampleLocationsSixteen = { 57 | glm::vec2{0.5625f, 0.5625f}, glm::vec2{0.4375f, 0.3125f}, 58 | glm::vec2{0.3125f, 0.625f}, glm::vec2{0.75f, 0.4375f}, 59 | glm::vec2{0.1875f, 0.375f}, glm::vec2{0.625f, 0.8125f}, 60 | glm::vec2{0.8125f, 0.6875f}, glm::vec2{0.6875f, 0.1875f}, 61 | glm::vec2{0.375f, 0.875f}, glm::vec2{0.5f, 0.0625f}, 62 | glm::vec2{0.25f, 0.125f}, glm::vec2{0.125f, 0.75f}, 63 | glm::vec2{0.0f, 0.5f}, glm::vec2{0.9375f, 0.25f}, 64 | glm::vec2{0.875f, 0.9375f}, glm::vec2{0.0625f, 0.0f}, 65 | }; 66 | 67 | // From: Multisampling: Standard sample locations 68 | // https://registry.khronos.org/vulkan/specs/1.3-khr-extensions/html/vkspec.html#primsrast-multisampling 69 | constexpr glm::vec2 GetSampleLocation(SampleCount sample_count, 70 | size_t location) { 71 | switch (sample_count) { 72 | case SampleCount::kOne: 73 | return kSampleLocationsOne[location % 1]; 74 | case SampleCount::kTwo: 75 | return kSampleLocationsTwo[location % 2]; 76 | case SampleCount::kFour: 77 | return kSampleLocationsFour[location % 4]; 78 | case SampleCount::kEight: 79 | return kSampleLocationsEight[location % 8]; 80 | case SampleCount::kSixteen: 81 | return kSampleLocationsSixteen[location % 16]; 82 | } 83 | return {0.5, 0.5}; 84 | } 85 | 86 | inline Color PerformResolve2(Color a, Color b) { 87 | return Color{(a.color & b.color) + (((a.color ^ b.color) >> 1) & 0x7F7F7F7F) + 88 | ((a.color ^ b.color) & 0x01010101)}; 89 | } 90 | 91 | inline Color PerformResolve(const Color* samples, uint8_t count) { 92 | if (count == 1) { 93 | return samples[0]; 94 | } 95 | auto* intermediates = 96 | reinterpret_cast(alloca(sizeof(Color) * count / 2)); 97 | for (size_t i = 0; i < count; i += 2) { 98 | intermediates[i / 2] = PerformResolve2(samples[i], samples[i + 1]); 99 | } 100 | return PerformResolve(intermediates, count / 2); 101 | } 102 | 103 | template >> 104 | class Texture { 105 | public: 106 | Texture(glm::ivec2 size, SampleCount samples = SampleCount::kOne) 107 | : Texture(reinterpret_cast( 108 | std::calloc(size.x * size.y * static_cast(samples), 109 | sizeof(T))), 110 | size, 111 | samples) {} 112 | 113 | ~Texture() { std::free(allocation_); } 114 | 115 | bool IsValid() const { return allocation_ != nullptr; } 116 | 117 | [[nodiscard]] bool Resize(glm::ivec2 size) { 118 | auto new_allocation = 119 | std::realloc(allocation_, size.x * size.y * sizeof(T) * 120 | static_cast(sample_count_)); 121 | if (!new_allocation) { 122 | // The old allocation is still valid. Nothing has changed. 123 | return false; 124 | } 125 | allocation_ = reinterpret_cast(new_allocation); 126 | size_ = size; 127 | return true; 128 | } 129 | 130 | [[nodiscard]] bool UpdateSampleCount(SampleCount sample_count) { 131 | if (sample_count_ == sample_count) { 132 | return true; 133 | } 134 | 135 | sample_count_ = sample_count; 136 | return Resize(size_); 137 | } 138 | 139 | void Set(const T& val, glm::ivec2 pos, size_t sample_index) { 140 | const auto sample_count = static_cast(sample_count_); 141 | const auto offset = ((size_.x * pos.y + pos.x) * sample_count) + 142 | (sample_index % sample_count); 143 | std::memcpy(allocation_ + offset, &val, sizeof(T)); 144 | } 145 | 146 | const T* Get(glm::ivec2 pos, size_t sample_index) const { 147 | const auto sample_count = static_cast(sample_count_); 148 | const auto offset = ((size_.x * pos.y + pos.x) * sample_count) + 149 | (sample_index % sample_count); 150 | return allocation_ + offset; 151 | } 152 | 153 | void Clear(const T& val) { 154 | for (auto i = 0; 155 | i < size_.x * size_.y * static_cast(sample_count_); i++) { 156 | allocation_[i] = val; 157 | } 158 | } 159 | 160 | constexpr size_t GetBytesPerPixel() const { return sizeof(T); } 161 | 162 | constexpr size_t GetByteLength() const { 163 | return GetLength() * GetBytesPerPixel(); 164 | } 165 | 166 | constexpr size_t GetLength() const { 167 | return size_.x * size_.y * static_cast(sample_count_); 168 | } 169 | 170 | std::pair GetMinMaxValue() const { 171 | if (sample_count_ != SampleCount::kOne) { 172 | return {}; 173 | } 174 | auto min = std::numeric_limits::max(); 175 | auto max = std::numeric_limits::min(); 176 | for (size_t i = 0, count = GetLength(); i < count; i++) { 177 | min = std::min(min, allocation_[i]); 178 | max = std::max(max, allocation_[i]); 179 | } 180 | return {min, max}; 181 | } 182 | 183 | std::shared_ptr CreateImage( 184 | std::function transform) const { 185 | if (sample_count_ != SampleCount::kOne) { 186 | return nullptr; 187 | } 188 | const auto size = GetLength() * sizeof(Color); 189 | auto* allocation = reinterpret_cast(std::malloc(size)); 190 | if (!allocation) { 191 | return nullptr; 192 | } 193 | for (size_t i = 0; i < GetLength(); i++) { 194 | allocation[i] = transform(allocation_[i]); 195 | } 196 | auto mapping = std::make_shared( 197 | reinterpret_cast(allocation), // 198 | size, // 199 | [allocation]() { std::free(allocation); } // 200 | ); 201 | return Image::Create(mapping, size_); 202 | } 203 | 204 | glm::ivec2 GetSize() const { return size_; } 205 | 206 | SampleCount GetSampleCount() const { return sample_count_; } 207 | 208 | [[nodiscard]] bool Resolve(Texture& to) const { 209 | if (to.GetSize() != GetSize()) { 210 | return false; 211 | } 212 | if (to.GetSampleCount() != SampleCount::kOne) { 213 | return false; 214 | } 215 | const auto slices = TileFactorForAvailableHardwareConcurrency(); 216 | glm::ivec2 span = size_ / glm::ivec2{slices, slices}; 217 | 218 | marl::WaitGroup wg; 219 | 220 | for (size_t x = 0; x < slices; x++) { 221 | for (size_t y = 0; y < slices; y++) { 222 | wg.add(); 223 | const auto min = glm::ivec2{span.x * x, span.y * y}; 224 | const auto max = glm::ivec2{span.x * (x + 1), span.y * (y + 1)}; 225 | marl::schedule([&, min, max]() { 226 | ResolveSubSection(to, min, max); 227 | wg.done(); 228 | }); 229 | } 230 | } 231 | wg.wait(); 232 | return true; 233 | } 234 | 235 | private: 236 | T* allocation_ = nullptr; 237 | glm::ivec2 size_ = {}; 238 | SampleCount sample_count_; 239 | 240 | Texture(T* allocation, glm::ivec2 size, SampleCount sample_count) 241 | : allocation_(allocation), size_(size), sample_count_(sample_count) {} 242 | 243 | void ResolveSubSection(Texture& to, glm::ivec2 min, glm::ivec2 max) const { 244 | min = glm::max(glm::ivec2{0, 0}, min); 245 | max = glm::min(size_, max); 246 | for (auto x = min.x; x < max.x; x++) { 247 | for (auto y = min.y; y < max.y; y++) { 248 | const auto position = glm::ivec2{x, y}; 249 | const auto* samples = Get(position, 0); 250 | to.Set(PerformResolve(samples, static_cast(sample_count_)), 251 | position, 0); 252 | } 253 | } 254 | } 255 | 256 | SFT_DISALLOW_COPY_AND_ASSIGN(Texture); 257 | }; 258 | 259 | } // namespace sft 260 | -------------------------------------------------------------------------------- /src/rasterizer/tiler.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "tiler.h" 7 | 8 | #include 9 | #include 10 | 11 | #include "marl/waitgroup.h" 12 | #include "rasterizer.h" 13 | 14 | namespace sft { 15 | 16 | Tiler::Tiler() = default; 17 | 18 | Tiler::~Tiler() = default; 19 | 20 | void Tiler::AddData(FragmentResources frag_resources) { 21 | const auto data = frag_resources_.emplace_back(std::move(frag_resources)); 22 | const auto min = glm::ivec2{glm::floor(data.box.GetLT())}; 23 | const auto max = glm::ivec2{glm::ceil(data.box.GetRB())}; 24 | tree_.Insert((int*)&min, (int*)&max, frag_resources_.size() - 1u); 25 | min_ = glm::min(min, min_); 26 | max_ = glm::max(max, max_); 27 | } 28 | 29 | void Tiler::Dispatch(Rasterizer& rasterizer) { 30 | const auto tile_factor = TileFactorForAvailableHardwareConcurrency(); 31 | const glm::ivec2 num_slices = {tile_factor, tile_factor}; 32 | const glm::ivec2 full_span = max_ - min_; 33 | const glm::ivec2 min_span = {64, 64}; 34 | const glm::ivec2 span = glm::max(full_span.x / num_slices, min_span); 35 | 36 | if (tree_.Count() == 0 || full_span.x <= 0 || full_span.y <= 0) { 37 | return; 38 | } 39 | 40 | using IndexSet = std::set>; 41 | 42 | marl::WaitGroup wg; 43 | 44 | for (auto x = min_.x; x < max_.x; x += span.x) { 45 | for (auto y = min_.y; y < max_.y; y += span.y) { 46 | IndexSet index_set; 47 | const auto min = glm::ivec2{x, y}; 48 | const auto max = min + span; 49 | auto found = tree_.Search((int*)&min, (int*)&max, 50 | [](size_t idx, void* ctx) -> bool { 51 | reinterpret_cast(ctx)->insert(idx); 52 | return true; 53 | }, 54 | &index_set); 55 | if (found == 0) { 56 | // The bounding boxes of no primitives intersect this tile. 57 | continue; 58 | } 59 | wg.add(); 60 | marl::schedule([&wg, min, max, index_set = std::move(index_set), 61 | &rasterizer, frag_resources = &frag_resources_]() { 62 | const auto tile = Rect::MakeLTRB(min.x, min.y, max.x, max.y); 63 | for (const auto& index : index_set) { 64 | rasterizer.ShadeFragments(frag_resources->at(index), tile); 65 | } 66 | wg.done(); 67 | }); 68 | } 69 | } 70 | wg.wait(); 71 | } 72 | 73 | void Tiler::Reset() { 74 | frag_resources_.clear(); 75 | tree_.RemoveAll(); 76 | min_ = {INT_MAX, INT_MAX}; 77 | max_ = {INT_MIN, INT_MIN}; 78 | } 79 | 80 | } // namespace sft 81 | -------------------------------------------------------------------------------- /src/rasterizer/tiler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include "geometry.h" 11 | #include "macros.h" 12 | #include "pipeline.h" 13 | #include "stage_resources.h" 14 | 15 | #define Min std::min 16 | #define Max std::max 17 | 18 | #ifndef __cdecl 19 | #define __cdecl 20 | #endif // __cdecl 21 | 22 | #include 23 | 24 | namespace sft { 25 | 26 | class Rasterizer; 27 | 28 | class Tiler { 29 | public: 30 | Tiler(); 31 | 32 | ~Tiler(); 33 | 34 | void Reset(); 35 | 36 | void AddData(FragmentResources frag_resources); 37 | 38 | void Dispatch(Rasterizer& rasterizer); 39 | 40 | private: 41 | std::vector frag_resources_; 42 | RTree tree_; 43 | glm::ivec2 min_ = {INT_MAX, INT_MAX}; 44 | glm::ivec2 max_ = {INT_MIN, INT_MIN}; 45 | 46 | SFT_DISALLOW_COPY_AND_ASSIGN(Tiler); 47 | }; 48 | 49 | } // namespace sft 50 | -------------------------------------------------------------------------------- /src/rasterizer/uniforms.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "uniforms.h" 7 | 8 | namespace sft {} // namespace sft 9 | -------------------------------------------------------------------------------- /src/rasterizer/uniforms.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include "buffer_view.h" 11 | #include "image.h" 12 | #include "macros.h" 13 | 14 | namespace sft { 15 | 16 | struct Uniforms { 17 | BufferView buffer; 18 | std::map> images; 19 | 20 | Uniforms() = default; 21 | 22 | Uniforms(const BufferView& view) : buffer(view) {} 23 | }; 24 | 25 | } // namespace sft 26 | -------------------------------------------------------------------------------- /src/rasterizer/vertex_descriptor.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #include "vertex_descriptor.h" 7 | -------------------------------------------------------------------------------- /src/rasterizer/vertex_descriptor.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This source file is part of the SFT project. 3 | * Licensed under the MIT License. See LICENSE file for details. 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | namespace sft { 11 | 12 | enum class IndexType { 13 | kUInt32, 14 | kUInt16, 15 | }; 16 | 17 | enum class VertexFormat { 18 | kFloat2, // like glm::vec2 19 | kFloat3, // like glm::vec3 20 | }; 21 | 22 | struct VertexDescriptor { 23 | size_t offset = 0; 24 | size_t stride = 0; 25 | IndexType index_type = IndexType::kUInt32; 26 | VertexFormat vertex_format = VertexFormat::kFloat3; 27 | }; 28 | 29 | } // namespace sft 30 | -------------------------------------------------------------------------------- /third_party/superliminal/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(superliminal INTERFACE) 2 | 3 | target_include_directories(superliminal INTERFACE .) 4 | -------------------------------------------------------------------------------- /third_party/superliminal/README.TXT: -------------------------------------------------------------------------------- 1 | 2 | TITLE 3 | 4 | R-TREES: A DYNAMIC INDEX STRUCTURE FOR SPATIAL SEARCHING 5 | 6 | DESCRIPTION 7 | 8 | A C++ templated version of the RTree algorithm. 9 | For more information please read the comments in RTree.h 10 | 11 | AUTHORS 12 | 13 | * 1983 Original algorithm and test code by Antonin Guttman and Michael Stonebraker, UC Berkely 14 | * 1994 ANCI C ported from original test code by Melinda Green - melinda@superliminal.com 15 | * 1995 Sphere volume fix for degeneracy problem submitted by Paul Brook 16 | * 2004 Templated C++ port by Greg Douglas 17 | 18 | LICENSE: 19 | 20 | Entirely free for all uses. Enjoy! 21 | 22 | FILES 23 | * RTree.h The C++ templated RTree implementation. Well commented. 24 | * Test.cpp A simple test program, ported from the original C version. 25 | * MemoryTest.cpp A more rigourous test to validate memory use. 26 | * README.TXT This file. 27 | 28 | TO BUILD 29 | 30 | To build a test, compile only one of the test files with RTree.h. 31 | Both test files contain a main() function. 32 | 33 | RECENT CHANGE LOG 34 | 35 | 05 Jan 2010 36 | o Fixed Iterator GetFirst() - Previous fix was not incomplete 37 | 38 | 03 Dec 2009 39 | o Added Iteartor GetBounds() 40 | o Added Iterator usage to simple test 41 | o Fixed Iterator GetFirst() - Thanks Mathew Riek 42 | o Minor updates for MSVC 2005/08 compilers 43 | 44 | -------------------------------------------------------------------------------- /tools/cmake/sft_library.cmake: -------------------------------------------------------------------------------- 1 | if(__sft_library) 2 | return() 3 | endif() 4 | set(__sft_library INCLUDED) 5 | 6 | macro(sft_library LIBRARY_NAME_ARG) 7 | 8 | add_library(${LIBRARY_NAME_ARG} ${ARGN}) 9 | 10 | target_include_directories(${LIBRARY_NAME_ARG} PUBLIC .) 11 | 12 | endmacro() 13 | 14 | macro(sft_executable EXECUTABLE_NAME_ARG) 15 | 16 | add_executable(${EXECUTABLE_NAME_ARG} ${ARGN}) 17 | 18 | endmacro() 19 | --------------------------------------------------------------------------------