├── .gitignore ├── CMakeLists.txt ├── Doxyfile.in ├── LICENSE ├── README.md ├── cmake ├── FindSDL2.cmake └── FindSDL2_image.cmake ├── data ├── box.obj └── box.png ├── docs ├── Half_Space_Rasterization_Mileff_Nehez_Dudra_2016.pdf ├── Lecture08.pdf └── README.md └── src ├── CMakeLists.txt ├── examples ├── Benchmark.cpp ├── Box.cpp ├── CMakeLists.txt ├── ObjData.cpp ├── ObjData.h ├── Random.cpp ├── Random.h ├── RasterizerTest.cpp ├── Texture.h ├── TextureExample.h ├── VertexProcessorTest.cpp └── vector_math.h └── renderer ├── CMakeLists.txt ├── EdgeData.h ├── EdgeEquation.h ├── IRasterizer.h ├── LineClipper.cpp ├── LineClipper.h ├── ParameterEquation.h ├── PixelData.h ├── PixelShaderBase.h ├── PolyClipper.cpp ├── PolyClipper.h ├── Rasterizer.h ├── Renderer.h ├── TriangleEquations.h ├── VertexCache.h ├── VertexConfig.h ├── VertexProcessor.cpp ├── VertexProcessor.h └── VertexShaderBase.h /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /.vscode 3 | /x64 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7) 2 | 3 | project("SoftwareRenderer") 4 | 5 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") 6 | 7 | if (EXISTS "${CMAKE_BINARY_DIR}/CMakeTools/CMakeToolsHelpers.cmake") 8 | include(${CMAKE_BINARY_DIR}/CMakeTools/CMakeToolsHelpers.cmake) 9 | endif () 10 | 11 | find_package(Doxygen) 12 | if (DOXYGEN_FOUND) 13 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY) 14 | add_custom_target(doc 15 | ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile 16 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 17 | COMMENT "Generating API documentation with Doxygen" VERBATIM 18 | ) 19 | endif () 20 | 21 | add_subdirectory(src) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2020 Markus Trenkwalder 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C++ Software Renderer/Rasterizer 2 | 3 | This project implements a C++ software renderer/rasterizer with vertex- and 4 | pixel shader support. 5 | 6 | ## Features 7 | * Generic vertex arrays for arbitrary data in the vertex processing stage. 8 | * Internal vertex cache for better vertex processing. 9 | * Affine and perspective correct per vertex parameter interpolation. 10 | * Vertex and pixel shaders written in C++ using some C++ template magic. 11 | 12 | ## Resources 13 | 14 | * [Triangle Rasterization](http://www.cs.unc.edu/~blloyd/comp770/Lecture08.pdf) 15 | * [Accelerated Half-Space Triangle Rasterization](https://www.researchgate.net/publication/286441992_Accelerated_Half-Space_Triangle_Rasterization) 16 | 17 | ## Example 18 | 19 | ```c++ 20 | 21 | #include "Renderer.h" 22 | 23 | // Define a pixel shader 24 | class PixelShader : public PixelShaderBase { 25 | public: 26 | static const bool InterpolateZ = true; 27 | static const bool InterpolateW = true; 28 | 29 | // Number of affine/linear variables used. 30 | static const int AVarCount = 3; 31 | 32 | // Number of perspective correct variables used. 33 | static const int PVarCount = 2; 34 | 35 | static void drawPixel(const PixelData &p) 36 | { 37 | ... 38 | } 39 | }; 40 | 41 | // Define a vertex shader 42 | class VertexShader : public VertexShaderBase { 43 | public: 44 | // Number of input vertex attributes used. 45 | static const int AttribCount = 1; 46 | 47 | static void processVertex(VertexShaderInput in, VertexShaderOutput *out) 48 | { 49 | // Write result to out. 50 | ... 51 | } 52 | }; 53 | 54 | // Use the renderer 55 | Rasterizer r; 56 | VertexProcessor v(&r); 57 | 58 | r.setRasterMode(RasterMode::Span); 59 | r.setScissorRect(0, 0, 640, 480); 60 | r.setPixelShader(); 61 | 62 | v.setViewport(0, 0, 640, 480); 63 | v.setCullMode(CullMode::CW); 64 | v.setVertexShader(); 65 | 66 | // Draw 67 | v.setVertexAttribPointer(0, sizeof(VertexData), vertexData); 68 | v.drawElements(DrawMode::Triangle, indexData.size(), indexData); 69 | 70 | ``` 71 | 72 | ## License 73 | 74 | This code is licensed under the MIT License (see [LICENSE](LICENSE)). -------------------------------------------------------------------------------- /cmake/FindSDL2.cmake: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved BSD 3-Clause License. See accompanying 2 | # file Copyright.txt or https://cmake.org/licensing for details. 3 | 4 | #.rst: 5 | # FindSDL2 6 | # ------- 7 | # 8 | # Locate SDL2 library 9 | # 10 | # This module defines 11 | # 12 | # :: 13 | # 14 | # SDL2_LIBRARY, the name of the library to link against 15 | # SDL2_FOUND, if false, do not try to link to SDL 16 | # SDL2_INCLUDE_DIR, where to find SDL.h 17 | # SDL2_VERSION_STRING, human-readable string containing the version of SDL 18 | # 19 | # 20 | # 21 | # This module responds to the flag: 22 | # 23 | # :: 24 | # 25 | # SDL2_BUILDING_LIBRARY 26 | # If this is defined, then no SDL2_main will be linked in because 27 | # only applications need main(). 28 | # Otherwise, it is assumed you are building an application and this 29 | # module will attempt to locate and set the proper link flags 30 | # as part of the returned SDL2_LIBRARY variable. 31 | # 32 | # 33 | # 34 | # Don't forget to include SDLmain.h and SDLmain.m your project for the 35 | # OS X framework based version. (Other versions link to -lSDLmain which 36 | # this module will try to find on your behalf.) Also for OS X, this 37 | # module will automatically add the -framework Cocoa on your behalf. 38 | # 39 | # 40 | # 41 | # Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your 42 | # configuration and no SDL2_LIBRARY, it means CMake did not find your SDL 43 | # library (SDL.dll, libsdl.so, SDL.framework, etc). Set 44 | # SDL2_LIBRARY_TEMP to point to your SDL library, and configure again. 45 | # Similarly, if you see an empty SDLMAIN_LIBRARY, you should set this 46 | # value as appropriate. These values are used to generate the final 47 | # SDL2_LIBRARY variable, but when these values are unset, SDL2_LIBRARY 48 | # does not get created. 49 | # 50 | # 51 | # 52 | # $SDL2DIR is an environment variable that would correspond to the 53 | # ./configure --prefix=$SDL2DIR used in building SDL. l.e.galup 9-20-02 54 | # 55 | # Modified by Eric Wing. Added code to assist with automated building 56 | # by using environmental variables and providing a more 57 | # controlled/consistent search behavior. Added new modifications to 58 | # recognize OS X frameworks and additional Unix paths (FreeBSD, etc). 59 | # Also corrected the header search path to follow "proper" SDL 60 | # guidelines. Added a search for SDLmain which is needed by some 61 | # platforms. Added a search for threads which is needed by some 62 | # platforms. Added needed compile switches for MinGW. 63 | # 64 | # On OSX, this will prefer the Framework version (if found) over others. 65 | # People will have to manually change the cache values of SDL2_LIBRARY to 66 | # override this selection or set the CMake environment 67 | # CMAKE_INCLUDE_PATH to modify the search paths. 68 | # 69 | # Note that the header path has changed from SDL/SDL.h to just SDL.h 70 | # This needed to change because "proper" SDL convention is #include 71 | # "SDL.h", not . This is done for portability reasons 72 | # because not all systems place things in SDL/ (see FreeBSD). 73 | 74 | if(NOT SDL2_DIR) 75 | set(SDL2_DIR "" CACHE PATH "SDL2 directory") 76 | endif() 77 | 78 | find_path(SDL2_INCLUDE_DIR SDL.h 79 | HINTS 80 | ENV SDL2DIR 81 | ${SDL2_DIR} 82 | PATH_SUFFIXES SDL2 83 | # path suffixes to search inside ENV{SDL2DIR} 84 | include/SDL2 include 85 | ) 86 | 87 | if(CMAKE_SIZEOF_VOID_P EQUAL 8) 88 | set(VC_LIB_PATH_SUFFIX lib/x64) 89 | else() 90 | set(VC_LIB_PATH_SUFFIX lib/x86) 91 | endif() 92 | 93 | find_library(SDL2_LIBRARY_TEMP 94 | NAMES SDL2 95 | HINTS 96 | ENV SDL2DIR 97 | ${SDL2_DIR} 98 | PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX} 99 | ) 100 | 101 | # Hide this cache variable from the user, it's an internal implementation 102 | # detail. The documented library variable for the user is SDL2_LIBRARY 103 | # which is derived from SDL2_LIBRARY_TEMP further below. 104 | set_property(CACHE SDL2_LIBRARY_TEMP PROPERTY TYPE INTERNAL) 105 | 106 | if(NOT SDL2_BUILDING_LIBRARY) 107 | if(NOT SDL2_INCLUDE_DIR MATCHES ".framework") 108 | # Non-OS X framework versions expect you to also dynamically link to 109 | # SDLmain. This is mainly for Windows and OS X. Other (Unix) platforms 110 | # seem to provide SDLmain for compatibility even though they don't 111 | # necessarily need it. 112 | find_library(SDL2MAIN_LIBRARY 113 | NAMES SDL2main 114 | HINTS 115 | ENV SDL2DIR 116 | ${SDL2_DIR} 117 | PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX} 118 | PATHS 119 | /sw 120 | /opt/local 121 | /opt/csw 122 | /opt 123 | ) 124 | endif() 125 | endif() 126 | 127 | # SDL may require threads on your system. 128 | # The Apple build may not need an explicit flag because one of the 129 | # frameworks may already provide it. 130 | # But for non-OSX systems, I will use the CMake Threads package. 131 | if(NOT APPLE) 132 | find_package(Threads) 133 | endif() 134 | 135 | # MinGW needs an additional link flag, -mwindows 136 | # It's total link flags should look like -lmingw32 -lSDLmain -lSDL -mwindows 137 | if(MINGW) 138 | set(MINGW32_LIBRARY mingw32 "-mwindows" CACHE STRING "link flags for MinGW") 139 | endif() 140 | 141 | if(SDL2_LIBRARY_TEMP) 142 | # For SDLmain 143 | if(SDL2MAIN_LIBRARY AND NOT SDL2_BUILDING_LIBRARY) 144 | list(FIND SDL2_LIBRARY_TEMP "${SDLMAIN_LIBRARY}" _SDL2_MAIN_INDEX) 145 | if(_SDL2_MAIN_INDEX EQUAL -1) 146 | set(SDL2_LIBRARY_TEMP "${SDLMAIN_LIBRARY}" ${SDL2_LIBRARY_TEMP}) 147 | endif() 148 | unset(_SDL2_MAIN_INDEX) 149 | endif() 150 | 151 | # For OS X, SDL uses Cocoa as a backend so it must link to Cocoa. 152 | # CMake doesn't display the -framework Cocoa string in the UI even 153 | # though it actually is there if I modify a pre-used variable. 154 | # I think it has something to do with the CACHE STRING. 155 | # So I use a temporary variable until the end so I can set the 156 | # "real" variable in one-shot. 157 | if(APPLE) 158 | set(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} "-framework Cocoa") 159 | endif() 160 | 161 | # For threads, as mentioned Apple doesn't need this. 162 | # In fact, there seems to be a problem if I used the Threads package 163 | # and try using this line, so I'm just skipping it entirely for OS X. 164 | if(NOT APPLE) 165 | set(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT}) 166 | endif() 167 | 168 | # For MinGW library 169 | if(MINGW) 170 | set(SDL2_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2_LIBRARY_TEMP}) 171 | endif() 172 | 173 | # Set the final string here so the GUI reflects the final state. 174 | set(SDL2_LIBRARY ${SDL2_LIBRARY_TEMP} CACHE STRING "Where the SDL Library can be found") 175 | endif() 176 | 177 | if(SDL2_INCLUDE_DIR AND EXISTS "${SDL2_INCLUDE_DIR}/SDL2_version.h") 178 | file(STRINGS "${SDL2_INCLUDE_DIR}/SDL2_version.h" SDL2_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL2_MAJOR_VERSION[ \t]+[0-9]+$") 179 | file(STRINGS "${SDL2_INCLUDE_DIR}/SDL2_version.h" SDL2_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL2_MINOR_VERSION[ \t]+[0-9]+$") 180 | file(STRINGS "${SDL2_INCLUDE_DIR}/SDL2_version.h" SDL2_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL2_PATCHLEVEL[ \t]+[0-9]+$") 181 | string(REGEX REPLACE "^#define[ \t]+SDL2_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_MAJOR "${SDL2_VERSION_MAJOR_LINE}") 182 | string(REGEX REPLACE "^#define[ \t]+SDL2_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_MINOR "${SDL2_VERSION_MINOR_LINE}") 183 | string(REGEX REPLACE "^#define[ \t]+SDL2_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_PATCH "${SDL2_VERSION_PATCH_LINE}") 184 | set(SDL2_VERSION_STRING ${SDL2_VERSION_MAJOR}.${SDL2_VERSION_MINOR}.${SDL2_VERSION_PATCH}) 185 | unset(SDL2_VERSION_MAJOR_LINE) 186 | unset(SDL2_VERSION_MINOR_LINE) 187 | unset(SDL2_VERSION_PATCH_LINE) 188 | unset(SDL2_VERSION_MAJOR) 189 | unset(SDL2_VERSION_MINOR) 190 | unset(SDL2_VERSION_PATCH) 191 | endif() 192 | 193 | set(SDL2_LIBRARIES ${SDL2_LIBRARY} ${SDL2MAIN_LIBRARY}) 194 | set(SDL2_INCLUDE_DIRS ${SDL2_INCLUDE_DIR}) 195 | 196 | include(FindPackageHandleStandardArgs) 197 | 198 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2 199 | REQUIRED_VARS SDL2_LIBRARY SDL2_INCLUDE_DIR 200 | VERSION_VAR SDL2_VERSION_STRING) 201 | -------------------------------------------------------------------------------- /cmake/FindSDL2_image.cmake: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved BSD 3-Clause License. See accompanying 2 | # file Copyright.txt or https://cmake.org/licensing for details. 3 | 4 | #.rst: 5 | # FindSDL2_image 6 | # ------------- 7 | # 8 | # Locate SDL2_image library 9 | # 10 | # This module defines: 11 | # 12 | # :: 13 | # 14 | # SDL2_IMAGE_LIBRARIES, the name of the library to link against 15 | # SDL2_IMAGE_INCLUDE_DIRS, where to find the headers 16 | # SDL2_IMAGE_FOUND, if false, do not try to link against 17 | # SDL2_IMAGE_VERSION_STRING - human-readable string containing the 18 | # version of SDL2_image 19 | # 20 | # 21 | # 22 | # For backward compatibility the following variables are also set: 23 | # 24 | # :: 25 | # 26 | # SDL2IMAGE_LIBRARY (same value as SDL2_IMAGE_LIBRARIES) 27 | # SDL2IMAGE_INCLUDE_DIR (same value as SDL2_IMAGE_INCLUDE_DIRS) 28 | # SDL2IMAGE_FOUND (same value as SDL2_IMAGE_FOUND) 29 | # 30 | # 31 | # 32 | # $SDLDIR is an environment variable that would correspond to the 33 | # ./configure --prefix=$SDLDIR used in building SDL. 34 | # 35 | # Created by Eric Wing. This was influenced by the FindSDL.cmake 36 | # module, but with modifications to recognize OS X frameworks and 37 | # additional Unix paths (FreeBSD, etc). 38 | 39 | if(NOT SDL2_IMAGE_INCLUDE_DIR AND SDL2IMAGE_INCLUDE_DIR) 40 | set(SDL2_IMAGE_INCLUDE_DIR ${SDL2IMAGE_INCLUDE_DIR} CACHE PATH "directory cache 41 | entry initialized from old variable name") 42 | endif() 43 | find_path(SDL2_IMAGE_INCLUDE_DIR SDL_image.h 44 | HINTS 45 | ENV SDL2IMAGEDIR 46 | ENV SDL2DIR 47 | ${SDL2_DIR} 48 | PATH_SUFFIXES SDL2 49 | # path suffixes to search inside ENV{SDL2DIR} 50 | include/SDL2 include 51 | ) 52 | 53 | if(CMAKE_SIZEOF_VOID_P EQUAL 8) 54 | set(VC_LIB_PATH_SUFFIX lib/x64) 55 | else() 56 | set(VC_LIB_PATH_SUFFIX lib/x86) 57 | endif() 58 | 59 | if(NOT SDL2_IMAGE_LIBRARY AND SDL2IMAGE_LIBRARY) 60 | set(SDL2_IMAGE_LIBRARY ${SDL2IMAGE_LIBRARY} CACHE FILEPATH "file cache entry 61 | initialized from old variable name") 62 | endif() 63 | find_library(SDL2_IMAGE_LIBRARY 64 | NAMES SDL2_image 65 | HINTS 66 | ENV SDL2IMAGEDIR 67 | ENV SDL2DIR 68 | ${SDL2_DIR} 69 | PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX} 70 | ) 71 | 72 | if(SDL2_IMAGE_INCLUDE_DIR AND EXISTS "${SDL2_IMAGE_INCLUDE_DIR}/SDL2_image.h") 73 | file(STRINGS "${SDL2_IMAGE_INCLUDE_DIR}/SDL2_image.h" SDL2_IMAGE_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL2_IMAGE_MAJOR_VERSION[ \t]+[0-9]+$") 74 | file(STRINGS "${SDL2_IMAGE_INCLUDE_DIR}/SDL2_image.h" SDL2_IMAGE_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL2_IMAGE_MINOR_VERSION[ \t]+[0-9]+$") 75 | file(STRINGS "${SDL2_IMAGE_INCLUDE_DIR}/SDL2_image.h" SDL2_IMAGE_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL2_IMAGE_PATCHLEVEL[ \t]+[0-9]+$") 76 | string(REGEX REPLACE "^#define[ \t]+SDL2_IMAGE_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_IMAGE_VERSION_MAJOR "${SDL2_IMAGE_VERSION_MAJOR_LINE}") 77 | string(REGEX REPLACE "^#define[ \t]+SDL2_IMAGE_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_IMAGE_VERSION_MINOR "${SDL2_IMAGE_VERSION_MINOR_LINE}") 78 | string(REGEX REPLACE "^#define[ \t]+SDL2_IMAGE_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_IMAGE_VERSION_PATCH "${SDL2_IMAGE_VERSION_PATCH_LINE}") 79 | set(SDL2_IMAGE_VERSION_STRING ${SDL2_IMAGE_VERSION_MAJOR}.${SDL2_IMAGE_VERSION_MINOR}.${SDL2_IMAGE_VERSION_PATCH}) 80 | unset(SDL2_IMAGE_VERSION_MAJOR_LINE) 81 | unset(SDL2_IMAGE_VERSION_MINOR_LINE) 82 | unset(SDL2_IMAGE_VERSION_PATCH_LINE) 83 | unset(SDL2_IMAGE_VERSION_MAJOR) 84 | unset(SDL2_IMAGE_VERSION_MINOR) 85 | unset(SDL2_IMAGE_VERSION_PATCH) 86 | endif() 87 | 88 | set(SDL2_IMAGE_LIBRARIES ${SDL2_IMAGE_LIBRARY}) 89 | set(SDL2_IMAGE_INCLUDE_DIRS ${SDL2_IMAGE_INCLUDE_DIR}) 90 | 91 | include(FindPackageHandleStandardArgs) 92 | 93 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2_image 94 | REQUIRED_VARS SDL2_IMAGE_LIBRARIES SDL2_IMAGE_INCLUDE_DIRS 95 | VERSION_VAR SDL2_IMAGE_VERSION_STRING) 96 | 97 | # for backward compatibility 98 | set(SDL2IMAGE_LIBRARY ${SDL2_IMAGE_LIBRARIES}) 99 | set(SDL2IMAGE_INCLUDE_DIR ${SDL2_IMAGE_INCLUDE_DIRS}) 100 | set(SDL2IMAGE_FOUND ${SDL2_IMAGE_FOUND}) 101 | 102 | mark_as_advanced(SDL2_IMAGE_LIBRARY SDL2_IMAGE_INCLUDE_DIR) 103 | -------------------------------------------------------------------------------- /data/box.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.78 (sub 0) OBJ File: '' 2 | # www.blender.org 3 | mtllib crate.mtl 4 | o Cube 5 | v 1.000000 -1.000000 -1.000000 6 | v 1.000000 -1.000000 1.000000 7 | v -1.000000 -1.000000 1.000000 8 | v -1.000000 -1.000000 -1.000000 9 | v 1.000000 1.000000 -0.999999 10 | v 0.999999 1.000000 1.000001 11 | v -1.000000 1.000000 1.000000 12 | v -1.000000 1.000000 -1.000000 13 | vt 0.0000 0.0000 14 | vt 1.0000 0.0000 15 | vt 1.0000 1.0000 16 | vt 0.0000 1.0000 17 | vt 0.0000 0.0000 18 | vt 1.0000 0.0000 19 | vt 1.0000 1.0000 20 | vt 0.0000 1.0000 21 | vt 1.0000 0.0000 22 | vt 1.0000 1.0000 23 | vt 0.0000 1.0000 24 | vt 0.0000 0.0000 25 | vt 1.0000 0.0000 26 | vt 0.0000 1.0000 27 | vt 0.0000 0.0000 28 | vt 1.0000 0.0000 29 | vt 1.0000 1.0000 30 | vt 1.0000 0.0000 31 | vt 1.0000 1.0000 32 | vt 0.0000 1.0000 33 | vn 0.0000 -1.0000 0.0000 34 | vn 0.0000 1.0000 0.0000 35 | vn 1.0000 0.0000 0.0000 36 | vn -0.0000 -0.0000 1.0000 37 | vn -1.0000 -0.0000 -0.0000 38 | vn 0.0000 0.0000 -1.0000 39 | usemtl Material 40 | s off 41 | f 1/1/1 2/2/1 3/3/1 4/4/1 42 | f 5/5/2 8/6/2 7/7/2 6/8/2 43 | f 1/1/3 5/9/3 6/10/3 2/11/3 44 | f 2/12/4 6/13/4 7/7/4 3/14/4 45 | f 3/15/5 7/16/5 8/17/5 4/4/5 46 | f 5/5/6 1/18/6 4/19/6 8/20/6 47 | -------------------------------------------------------------------------------- /data/box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trenki2/SoftwareRenderer/552e9f5f1a0a221679cae1403cae83dfd94bf21f/data/box.png -------------------------------------------------------------------------------- /docs/Half_Space_Rasterization_Mileff_Nehez_Dudra_2016.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trenki2/SoftwareRenderer/552e9f5f1a0a221679cae1403cae83dfd94bf21f/docs/Half_Space_Rasterization_Mileff_Nehez_Dudra_2016.pdf -------------------------------------------------------------------------------- /docs/Lecture08.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trenki2/SoftwareRenderer/552e9f5f1a0a221679cae1403cae83dfd94bf21f/docs/Lecture08.pdf -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Resources 2 | 3 | * [Triangle Rasterization](https://www.cs.unc.edu/xcms/courses/comp770-s07/Lecture08.pdf) 4 | * [Accelerated Half-Space Triangle Rasterization](https://www.researchgate.net/publication/286441992_Accelerated_Half-Space_Triangle_Rasterization) 5 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(renderer) 2 | add_subdirectory(examples) -------------------------------------------------------------------------------- /src/examples/Benchmark.cpp: -------------------------------------------------------------------------------- 1 | #include "Renderer.h" 2 | #include "Random.h" 3 | #include 4 | #include 5 | #include 6 | using namespace swr; 7 | 8 | struct VertexData 9 | { 10 | float x, y, z; 11 | float r, g, b; 12 | }; 13 | 14 | struct PixelShader : public PixelShaderBase 15 | { 16 | static const int AVarCount = 3; 17 | 18 | static std::vector buffer; 19 | static int width; 20 | static int height; 21 | 22 | static void drawPixel(const PixelData& p) 23 | { 24 | buffer[p.x + width * p.y] = 1; 25 | } 26 | }; 27 | 28 | std::vector PixelShader::buffer; 29 | int PixelShader::width; 30 | int PixelShader::height; 31 | 32 | struct VertexShader : public VertexShaderBase 33 | { 34 | static const int AttribCount = 1; 35 | static const int AVarCount = 3; 36 | static const int PVarCount = 0; 37 | 38 | static void processVertex(VertexShaderInput in, VertexShaderOutput* out) 39 | { 40 | const VertexData* data = static_cast(in[0]); 41 | out->x = data->x; 42 | out->y = data->y; 43 | out->z = data->z; 44 | out->w = 1.0f; 45 | out->avar[0] = data->r; 46 | out->avar[1] = data->g; 47 | out->avar[2] = data->b; 48 | } 49 | }; 50 | 51 | class Benchmark { 52 | private: 53 | VertexData CreateVertex(Random& random) 54 | { 55 | VertexData vertex; 56 | vertex.x = (float)random.NextDouble(); 57 | vertex.y = (float)random.NextDouble(); 58 | vertex.z = (float)random.NextDouble(); 59 | vertex.r = (float)random.NextDouble(); 60 | vertex.g = (float)random.NextDouble(); 61 | vertex.b = (float)random.NextDouble(); 62 | return vertex; 63 | } 64 | 65 | public: 66 | void Run() 67 | { 68 | PixelShader::buffer.resize(640 * 480); 69 | PixelShader::width = 640; 70 | PixelShader::height = 480; 71 | 72 | Rasterizer r; 73 | VertexProcessor v(&r); 74 | 75 | r.setScissorRect(0, 0, 640, 480); 76 | v.setViewport(0, 0, 640, 480); 77 | v.setCullMode(CullMode::None); 78 | 79 | std::vector indices; 80 | std::vector vertices; 81 | 82 | Random random(0); 83 | 84 | for (int i = 0; i < 4096 * 10; i++) 85 | { 86 | auto offset = vertices.size(); 87 | 88 | vertices.push_back(CreateVertex(random)); 89 | vertices.push_back(CreateVertex(random)); 90 | vertices.push_back(CreateVertex(random)); 91 | 92 | indices.push_back(offset + 0); 93 | indices.push_back(offset + 1); 94 | indices.push_back(offset + 2); 95 | } 96 | 97 | r.setPixelShader(); 98 | v.setVertexShader(); 99 | v.setVertexAttribPointer(0, sizeof(VertexData), &vertices[0]); 100 | 101 | auto start = std::chrono::steady_clock::now(); 102 | v.drawElements(DrawMode::Triangle, indices.size(), &indices[0]); 103 | auto end = std::chrono::steady_clock::now(); 104 | std::cout << "Elapsed: " << std::chrono::duration_cast(end - start).count() << std::endl; 105 | } 106 | }; 107 | 108 | int main(int argc, char* argv[]) 109 | { 110 | Benchmark b; 111 | b.Run(); 112 | } -------------------------------------------------------------------------------- /src/examples/Box.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2020 Markus Trenkwalder 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | #include "SDL.h" 26 | #include "SDL_image.h" 27 | #include "Renderer.h" 28 | #include "ObjData.h" 29 | #include "vector_math.h" 30 | #include "Texture.h" 31 | #include 32 | 33 | typedef vmath::vec3 vec3f; 34 | typedef vmath::vec4 vec4f; 35 | typedef vmath::mat4 mat4f; 36 | 37 | using namespace swr; 38 | 39 | class PixelShader : public PixelShaderBase { 40 | public: 41 | static const bool InterpolateZ = false; 42 | static const bool InterpolateW = true; // Required for perspective correct texturing 43 | static const int AVarCount = 0; 44 | static const int PVarCount = 2; // UV coordinates 45 | 46 | static SDL_Surface* surface; 47 | static std::shared_ptr texture; 48 | 49 | static void drawPixel(const PixelData &p) 50 | { 51 | // Compute texture coordinate derivatives 52 | float dudx, dudy, dvdx, dvdy; 53 | p.computePerspectiveDerivatives(*p.equations, 0, dudx, dudy); // U derivatives 54 | p.computePerspectiveDerivatives(*p.equations, 1, dvdx, dvdy); // V derivatives 55 | 56 | Uint32 sampledColor; 57 | texture->sample(p.pvar[0], p.pvar[1], dudx, dvdx, dudy, dvdy, sampledColor); 58 | 59 | Uint32 *screenBuffer = (Uint32*)((Uint8 *)surface->pixels + (size_t)p.y * (size_t)surface->pitch + (size_t)p.x * 4); 60 | *screenBuffer = sampledColor; 61 | } 62 | }; 63 | 64 | SDL_Surface* PixelShader::surface; 65 | std::shared_ptr PixelShader::texture; 66 | 67 | class VertexShader : public VertexShaderBase { 68 | public: 69 | static const int AttribCount = 1; 70 | static const int AVarCount = 0; 71 | static const int PVarCount = 2; 72 | 73 | static mat4f modelViewProjectionMatrix; 74 | 75 | static void processVertex(VertexShaderInput in, VertexShaderOutput *out) 76 | { 77 | const ObjData::VertexArrayData *data = static_cast(in[0]); 78 | 79 | vec4f position = modelViewProjectionMatrix * vec4f(data->vertex, 1.0f); 80 | 81 | out->x = position.x; 82 | out->y = position.y; 83 | out->z = position.z; 84 | out->w = position.w; 85 | out->pvar[0] = data->texcoord.x; 86 | out->pvar[1] = data->texcoord.y; 87 | } 88 | }; 89 | 90 | mat4f VertexShader::modelViewProjectionMatrix; 91 | 92 | int main(int argc, char *argv[]) 93 | { 94 | if (SDL_Init(SDL_INIT_VIDEO) < 0) { 95 | fprintf(stderr, "SDL could not initialize! SDL_Error: %s\n", SDL_GetError()); 96 | return 1; 97 | } 98 | 99 | // Initialize SDL_image 100 | int imgFlags = IMG_INIT_PNG; 101 | if (!(IMG_Init(imgFlags) & imgFlags)) { 102 | fprintf(stderr, "SDL_image could not initialize! SDL_image Error: %s\n", IMG_GetError()); 103 | SDL_Quit(); 104 | return 1; 105 | } 106 | 107 | SDL_Window *window = SDL_CreateWindow( 108 | "Box", 109 | SDL_WINDOWPOS_UNDEFINED, 110 | SDL_WINDOWPOS_UNDEFINED, 111 | 640, 112 | 480, 113 | 0 114 | ); 115 | 116 | if (!window) { 117 | fprintf(stderr, "Window could not be created! SDL_Error: %s\n", SDL_GetError()); 118 | IMG_Quit(); 119 | SDL_Quit(); 120 | return 1; 121 | } 122 | 123 | SDL_Surface *screen = SDL_GetWindowSurface(window); 124 | if (!screen) { 125 | fprintf(stderr, "Could not get window surface! SDL_Error: %s\n", SDL_GetError()); 126 | SDL_DestroyWindow(window); 127 | IMG_Quit(); 128 | SDL_Quit(); 129 | return 1; 130 | } 131 | 132 | try { 133 | SDL_Surface *tmp = IMG_Load("data/box.png"); 134 | if (!tmp) { 135 | throw std::runtime_error(std::string("Could not load texture! SDL_image Error: ") + IMG_GetError()); 136 | } 137 | 138 | SDL_Surface *baseTex = SDL_ConvertSurface(tmp, screen->format, 0); 139 | SDL_FreeSurface(tmp); 140 | 141 | if (!baseTex) { 142 | throw std::runtime_error(std::string("Could not convert texture surface! SDL Error: ") + SDL_GetError()); 143 | } 144 | 145 | // Create texture with mipmaps 146 | PixelShader::texture = std::make_shared(baseTex); 147 | PixelShader::surface = screen; 148 | 149 | std::vector vdata; 150 | std::vector idata; 151 | ObjData::loadFromFile("data/box.obj").toVertexArray(vdata, idata); 152 | 153 | Rasterizer r; 154 | VertexProcessor v(&r); 155 | 156 | r.setRasterMode(RasterMode::Span); 157 | r.setScissorRect(0, 0, 640, 480); 158 | r.setPixelShader(); 159 | 160 | v.setViewport(0, 0, 640, 480); 161 | v.setCullMode(CullMode::CW); 162 | v.setVertexShader(); 163 | 164 | // Animation and timing variables 165 | Uint32 lastFrameTime = SDL_GetTicks(); 166 | Uint32 lastFPSUpdate = lastFrameTime; 167 | int frameCount = 0; 168 | float angle = 0.0f; 169 | const float angularSpeed = 0.5f; // Radians per second 170 | 171 | bool running = true; 172 | SDL_Event e; 173 | 174 | while (running) { 175 | // Handle events 176 | while (SDL_PollEvent(&e)) { 177 | if (e.type == SDL_QUIT) { 178 | running = false; 179 | break; 180 | } 181 | } 182 | 183 | // Calculate frame timing 184 | Uint32 currentTime = SDL_GetTicks(); 185 | float deltaTime = (currentTime - lastFrameTime) / 1000.0f; 186 | lastFrameTime = currentTime; 187 | 188 | // Update camera position 189 | angle += angularSpeed * deltaTime; 190 | float camX = 5.0f * cos(angle); 191 | float camZ = 5.0f * sin(angle); 192 | 193 | mat4f lookAtMatrix = vmath::lookat_matrix(vec3f(camX, 2.0f, camZ), vec3f(0.0f), vec3f(0.0f, 1.0f, 0.0f)); 194 | mat4f perspectiveMatrix = vmath::perspective_matrix(60.0f, 4.0f / 3.0f, 0.1f, 10.0f); 195 | VertexShader::modelViewProjectionMatrix = perspectiveMatrix * lookAtMatrix; 196 | 197 | // Clear screen (set to black) 198 | SDL_FillRect(screen, NULL, 0); 199 | 200 | // Draw the box 201 | v.setVertexAttribPointer(0, sizeof(ObjData::VertexArrayData), &vdata[0]); 202 | v.drawElements(DrawMode::Triangle, idata.size(), &idata[0]); 203 | 204 | SDL_UpdateWindowSurface(window); 205 | 206 | // Update FPS counter every second 207 | frameCount++; 208 | if (currentTime - lastFPSUpdate >= 1000) { 209 | float fps = frameCount * 1000.0f / (currentTime - lastFPSUpdate); 210 | char title[64]; 211 | snprintf(title, sizeof(title), "Box - FPS: %.1f", fps); 212 | SDL_SetWindowTitle(window, title); 213 | 214 | frameCount = 0; 215 | lastFPSUpdate = currentTime; 216 | } 217 | 218 | SDL_Delay(0); 219 | } 220 | 221 | } catch (const std::exception& e) { 222 | fprintf(stderr, "Error: %s\n", e.what()); 223 | SDL_DestroyWindow(window); 224 | IMG_Quit(); 225 | SDL_Quit(); 226 | return 1; 227 | } 228 | 229 | // Cleanup 230 | SDL_DestroyWindow(window); 231 | IMG_Quit(); 232 | SDL_Quit(); 233 | 234 | return 0; 235 | } 236 | -------------------------------------------------------------------------------- /src/examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7) 2 | 3 | project(SoftwareRendererExamples) 4 | 5 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../renderer) 6 | 7 | find_package(OpenMP) 8 | if (OPENMP_FOUND) 9 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") 10 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") 11 | endif () 12 | 13 | find_package(SDL2 REQUIRED) 14 | find_package(SDL2_image REQUIRED) 15 | 16 | include_directories(${SDL2_INCLUDE_DIRS} ${SDL2_IMAGE_INCLUDE_DIRS}) 17 | 18 | add_executable(RasterizerTest RasterizerTest.cpp) 19 | target_link_libraries(RasterizerTest renderer ${SDL2_LIBRARIES}) 20 | 21 | add_executable(VertexProcessorTest VertexProcessorTest.cpp) 22 | target_link_libraries(VertexProcessorTest renderer ${SDL2_LIBRARIES}) 23 | 24 | add_executable(Box Box.cpp ObjData.cpp ObjData.h) 25 | target_link_libraries(Box renderer ${SDL2_LIBRARIES} ${SDL2_IMAGE_LIBRARIES}) 26 | 27 | add_executable(Benchmark Benchmark.cpp Random.cpp Random.h) 28 | target_link_libraries(Benchmark renderer) -------------------------------------------------------------------------------- /src/examples/ObjData.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2020 Markus Trenkwalder 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | #include "ObjData.h" 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | 34 | using namespace std; 35 | 36 | typedef vmath::vec3 vec3f; 37 | typedef vmath::vec2 vec2f; 38 | 39 | // string is supposed to be in the form //, 40 | void getIndicesFromString(string str, unsigned &vi, unsigned &ni, unsigned &ti) 41 | { 42 | string v; 43 | string t; 44 | string n; 45 | 46 | string *tmp[3] = {&v, &t, &n}; 47 | 48 | int tidx = 0; 49 | string *target = tmp[0]; 50 | 51 | for (size_t i = 0; i < str.size(); ++i) { 52 | if (str[i] == '/') target = tmp[++tidx]; 53 | else *target += str[i]; 54 | } 55 | 56 | istringstream iss1(v); 57 | vi = 0; 58 | iss1 >> vi; 59 | 60 | istringstream iss2(t); 61 | ti = 0; 62 | iss2 >> ti; 63 | 64 | istringstream iss3(n); 65 | ni = 0; 66 | iss3 >> ni; 67 | } 68 | 69 | ObjData ObjData::loadFromFile(const char *filename) 70 | { 71 | ObjData result; 72 | result.vertices.push_back(vec3f(0.0f)); 73 | result.normals.push_back(vec3f(0.0f)); 74 | result.texcoords.push_back(vec2f(0.0)); 75 | 76 | ifstream in(filename, ios::in); 77 | while (in) { 78 | string line; 79 | getline(in, line); 80 | istringstream iss; 81 | iss.str(line); 82 | string cmd; 83 | iss >> cmd; 84 | 85 | if (cmd[0] == '#') continue; 86 | else if (cmd == "v") { 87 | vec3f v; 88 | iss >> v.x >> v.y >> v.z; 89 | result.vertices.push_back(v); 90 | } else if (cmd == "vn") { 91 | vec3f v; 92 | iss >> v.x >> v.y >> v.z; 93 | result.normals.push_back(v); 94 | } else if (cmd == "vt") { 95 | vec2f t; 96 | iss >> t.x >> t.y; 97 | result.texcoords.push_back(t); 98 | } else if (cmd == "f") { 99 | result.faces.push_back(ObjData::Face()); 100 | ObjData::Face &face = result.faces.back(); 101 | 102 | while (iss) { 103 | string word; 104 | iss >> word; 105 | 106 | if (word == "") continue; 107 | 108 | ObjData::VertexRef vr; 109 | getIndicesFromString(word, vr.vertexIndex, vr.normalIndex, vr.texcoordIndex); 110 | 111 | face.push_back(vr); 112 | } 113 | } 114 | } 115 | 116 | return result; 117 | } 118 | 119 | namespace internal { 120 | struct VertexRefCompare { 121 | bool operator () (const ObjData::VertexRef &v1, const ObjData::VertexRef &v2) const 122 | { 123 | if (v1.vertexIndex < v2.vertexIndex) return true; 124 | else if (v1.vertexIndex > v2.vertexIndex) return false; 125 | else { 126 | if (v1.normalIndex < v2.normalIndex) return true; 127 | else if (v1.normalIndex > v2.normalIndex) return false; 128 | else { 129 | if (v1.texcoordIndex < v2.texcoordIndex) return true; 130 | else return false; 131 | } 132 | } 133 | } 134 | }; 135 | } 136 | 137 | using namespace internal; 138 | 139 | void ObjData::toVertexArray(std::vector &vdata, std::vector &idata) 140 | { 141 | vdata.clear(); 142 | idata.clear(); 143 | 144 | struct Helper { 145 | vector &m_vdata; 146 | vector &m_idata; 147 | const ObjData &m_obj; 148 | 149 | typedef map vertex_index_map_t; 150 | vertex_index_map_t vertex_index_map; 151 | 152 | Helper(const ObjData& obj, std::vector &vdata, std::vector &idata): 153 | m_vdata(vdata), m_idata(idata), m_obj(obj) {} 154 | 155 | void process() 156 | { 157 | for (size_t i = 0; i < m_obj.faces.size(); ++i) { 158 | unsigned i1 = addVertex(m_obj.faces[i][0]); 159 | 160 | // make a triangle fan if there are more than 3 vertices 161 | for (size_t j = 2; j < m_obj.faces[i].size(); ++j) { 162 | unsigned i2 = addVertex(m_obj.faces[i][j-1]); 163 | unsigned i3 = addVertex(m_obj.faces[i][j]); 164 | 165 | addFace(i1, i2, i3); 166 | } 167 | } 168 | } 169 | 170 | unsigned addVertex(const VertexRef &v) 171 | { 172 | // check if this vertex already exists 173 | vertex_index_map_t::const_iterator it = vertex_index_map.find(v); 174 | if (it != vertex_index_map.end()) 175 | return it->second; 176 | 177 | // does not exist, so insert a new one 178 | unsigned i = static_cast(vertex_index_map.size()); 179 | vertex_index_map.insert(make_pair(v, i)); 180 | 181 | VertexArrayData vd; 182 | vd.vertex = m_obj.vertices[v.vertexIndex]; 183 | vd.normal = m_obj.normals[v.normalIndex]; 184 | vd.texcoord = m_obj.texcoords[v.texcoordIndex]; 185 | m_vdata.push_back(vd); 186 | 187 | return i; 188 | } 189 | 190 | void addFace(int i1, int i2, int i3) 191 | { 192 | m_idata.push_back(i1); 193 | m_idata.push_back(i2); 194 | m_idata.push_back(i3); 195 | } 196 | 197 | } helper(*this, vdata, idata); 198 | 199 | helper.process(); 200 | } 201 | -------------------------------------------------------------------------------- /src/examples/ObjData.h: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2020 Markus Trenkwalder 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "vector_math.h" 28 | #include 29 | 30 | // Use this class to load Obj files from disk 31 | // and convert them to vertex arrays. 32 | struct ObjData { 33 | struct VertexArrayData { 34 | vmath::vec3 vertex; 35 | vmath::vec3 normal; 36 | vmath::vec2 texcoord; 37 | }; 38 | 39 | struct VertexRef { 40 | unsigned vertexIndex; 41 | unsigned normalIndex; 42 | unsigned texcoordIndex; 43 | }; 44 | 45 | typedef std::vector Face; 46 | 47 | std::vector< vmath::vec3 > vertices; 48 | std::vector< vmath::vec3 > normals; 49 | std::vector< vmath::vec2 > texcoords; 50 | std::vector faces; 51 | 52 | // Load the .obj file 53 | static ObjData loadFromFile(const char *filename); 54 | 55 | // Convert to vertex and index array 56 | void toVertexArray(std::vector &vdata, std::vector &idata); 57 | }; 58 | 59 | -------------------------------------------------------------------------------- /src/examples/Random.cpp: -------------------------------------------------------------------------------- 1 | #include "Random.h" 2 | 3 | Random::Random() : Random(0) 4 | { 5 | } 6 | 7 | Random::Random(int Seed) 8 | { 9 | int ii; 10 | int mj, mk; 11 | 12 | //Initialize our Seed array. 13 | //This algorithm comes from Numerical Recipes in C (2nd Ed.) 14 | int subtraction = (Seed == std::numeric_limits::min()) ? std::numeric_limits::max() : std::abs(Seed); 15 | mj = MSEED - subtraction; 16 | SeedArray[55] = mj; 17 | mk = 1; 18 | for (int i = 1; i < 55; i++) 19 | { //Apparently the range [1..55] is special (Knuth) and so we're wasting the 0'th position. 20 | ii = (21 * i) % 55; 21 | SeedArray[ii] = mk; 22 | mk = mj - mk; 23 | if (mk < 0) 24 | { 25 | mk += MBIG; 26 | } 27 | mj = SeedArray[ii]; 28 | } 29 | for (int k = 1; k < 5; k++) 30 | { 31 | for (int i = 1; i < 56; i++) 32 | { 33 | SeedArray[i] -= SeedArray[1 + (i + 30) % 55]; 34 | if (SeedArray[i] < 0) 35 | { 36 | SeedArray[i] += MBIG; 37 | } 38 | } 39 | } 40 | inext = 0; 41 | inextp = 21; 42 | Seed = 1; 43 | } 44 | 45 | double Random::Sample() 46 | { 47 | //Including this division at the end gives us significantly improved 48 | //random number distribution. 49 | return (InternalSample() * (1.0 / MBIG)); 50 | } 51 | 52 | int Random::InternalSample() 53 | { 54 | int retVal; 55 | int locINext = inext; 56 | int locINextp = inextp; 57 | 58 | if (++locINext >= 56) 59 | { 60 | locINext = 1; 61 | } 62 | if (++locINextp >= 56) 63 | { 64 | locINextp = 1; 65 | } 66 | 67 | retVal = SeedArray[locINext] - SeedArray[locINextp]; 68 | 69 | if (retVal == MBIG) 70 | { 71 | retVal--; 72 | } 73 | if (retVal < 0) 74 | { 75 | retVal += MBIG; 76 | } 77 | 78 | SeedArray[locINext] = retVal; 79 | 80 | inext = locINext; 81 | inextp = locINextp; 82 | 83 | return retVal; 84 | } 85 | 86 | int Random::Next() 87 | { 88 | return InternalSample(); 89 | } 90 | 91 | double Random::GetSampleForLargeRange() 92 | { 93 | // The distribution of double value returned by Sample 94 | // is not distributed well enough for a large range. 95 | // If we use Sample for a range [Int32.MinValue..Int32.MaxValue) 96 | // We will end up getting even numbers only. 97 | 98 | int result = InternalSample(); 99 | // Note we can't use addition here. The distribution will be bad if we do that. 100 | bool negative = (InternalSample() % 2 == 0) ? true : false; // decide the sign based on second sample 101 | if (negative) 102 | { 103 | result = -result; 104 | } 105 | double d = result; 106 | d += (std::numeric_limits::max() - 1); // get a number in range [0 .. 2 * Int32MaxValue - 1) 107 | d /= 2 * static_cast(std::numeric_limits::max()) - 1; 108 | return d; 109 | } 110 | 111 | int Random::Next(int minValue, int maxValue) 112 | { 113 | long long range = static_cast(maxValue) - minValue; 114 | if (range <= static_cast(std::numeric_limits::max())) 115 | { 116 | return (static_cast(Sample() * range) + minValue); 117 | } 118 | else 119 | { 120 | return static_cast(static_cast(GetSampleForLargeRange() * range) + minValue); 121 | } 122 | } 123 | 124 | int Random::Next(int maxValue) 125 | { 126 | return static_cast(Sample() * maxValue); 127 | } 128 | 129 | double Random::NextDouble() 130 | { 131 | return Sample(); 132 | } 133 | 134 | void Random::NextBytes(std::vector& buffer) 135 | { 136 | if (buffer.empty()) 137 | { 138 | throw std::invalid_argument("buffer"); 139 | } 140 | 141 | for (int i = 0; i < buffer.size(); i++) 142 | { 143 | buffer[i] = static_cast(InternalSample() % (std::numeric_limits::max() + 1)); 144 | } 145 | } -------------------------------------------------------------------------------- /src/examples/Random.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | //C# TO C++ CONVERTER NOTE: The following .NET attribute has no direct equivalent in C++: 9 | //ORIGINAL LINE: [Serializable] public class Random 10 | class Random 11 | { 12 | // 13 | // Private Constants 14 | // 15 | private: 16 | static constexpr int MBIG = std::numeric_limits::max(); 17 | static constexpr int MSEED = 161803398; 18 | static constexpr int MZ = 0; 19 | 20 | 21 | // 22 | // Member Variables 23 | // 24 | int inext = 0; 25 | int inextp = 0; 26 | std::vector SeedArray = std::vector(56); 27 | 28 | // 29 | // Public Constants 30 | // 31 | 32 | // 33 | // Native Declarations 34 | // 35 | 36 | // 37 | // Constructors 38 | // 39 | 40 | public: 41 | Random(); 42 | 43 | Random(int Seed); 44 | 45 | // 46 | // Package Private Methods 47 | // 48 | 49 | /*====================================Sample==================================== 50 | **Action: Return a new random number [0..1) and reSeed the Seed array. 51 | **Returns: A double [0..1) 52 | **Arguments: None 53 | **Exceptions: None 54 | ==============================================================================*/ 55 | protected: 56 | virtual double Sample(); 57 | 58 | private: 59 | int InternalSample(); 60 | 61 | // 62 | // Public Instance Methods 63 | // 64 | 65 | 66 | /*=====================================Next===================================== 67 | **Returns: An int [0..Int32.MaxValue) 68 | **Arguments: None 69 | **Exceptions: None. 70 | ==============================================================================*/ 71 | public: 72 | virtual int Next(); 73 | 74 | private: 75 | double GetSampleForLargeRange(); 76 | 77 | 78 | /*=====================================Next===================================== 79 | **Returns: An int [minvalue..maxvalue) 80 | **Arguments: minValue -- the least legal value for the Random number. 81 | ** maxValue -- One greater than the greatest legal return value. 82 | **Exceptions: None. 83 | ==============================================================================*/ 84 | public: 85 | virtual int Next(int minValue, int maxValue); 86 | 87 | 88 | /*=====================================Next===================================== 89 | **Returns: An int [0..maxValue) 90 | **Arguments: maxValue -- One more than the greatest legal return value. 91 | **Exceptions: None. 92 | ==============================================================================*/ 93 | virtual int Next(int maxValue); 94 | 95 | 96 | /*=====================================Next===================================== 97 | **Returns: A double [0..1) 98 | **Arguments: None 99 | **Exceptions: None 100 | ==============================================================================*/ 101 | virtual double NextDouble(); 102 | 103 | 104 | /*==================================NextBytes=================================== 105 | **Action: Fills the byte array with random bytes [0..0x7f]. The entire array is filled. 106 | **Returns:Void 107 | **Arugments: buffer -- the array to be filled. 108 | **Exceptions: None 109 | ==============================================================================*/ 110 | virtual void NextBytes(std::vector &buffer); 111 | }; 112 | -------------------------------------------------------------------------------- /src/examples/RasterizerTest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2020 Markus Trenkwalder 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | #include "SDL.h" 26 | #include "Rasterizer.h" 27 | 28 | using namespace swr; 29 | 30 | class PixelShader : public PixelShaderBase { 31 | public: 32 | static const bool InterpolateZ = false; 33 | static const bool InterpolateW = false; 34 | static const int AVarCount = 3; 35 | 36 | static SDL_Surface* surface; 37 | 38 | static void drawPixel(const PixelData &p) 39 | { 40 | int rint = (int)(p.avar[0] * 255); 41 | int gint = (int)(p.avar[1] * 255); 42 | int bint = (int)(p.avar[2] * 255); 43 | 44 | Uint32 color = rint << 16 | gint << 8 | bint; 45 | 46 | Uint32 *buffer = (Uint32*)((Uint8 *)surface->pixels + (int)p.y * surface->pitch + (int)p.x * 4); 47 | *buffer = color; 48 | } 49 | }; 50 | 51 | SDL_Surface* PixelShader::surface; 52 | 53 | void drawTriangle(SDL_Surface *screen) 54 | { 55 | Rasterizer r; 56 | r.setScissorRect(0, 0, 640, 480); 57 | r.setPixelShader(); 58 | PixelShader::surface = screen; 59 | 60 | RasterizerVertex v0, v1, v2; 61 | 62 | v0.x = 320; 63 | v0.y = 100; 64 | v0.avar[0] = 1.0f; 65 | v0.avar[1] = 0.0f; 66 | v0.avar[2] = 0.0f; 67 | 68 | v1.x = 480; 69 | v1.y = 200; 70 | v1.avar[0] = 0.0f; 71 | v1.avar[1] = 1.0f; 72 | v1.avar[2] = 0.0f; 73 | 74 | v2.x = 120; 75 | v2.y = 300; 76 | v2.avar[0] = 0.0f; 77 | v2.avar[1] = 0.0f; 78 | v2.avar[2] = 1.0f; 79 | 80 | r.drawTriangle(v0, v1, v2); 81 | } 82 | 83 | int main(int argc, char *argv[]) 84 | { 85 | SDL_Init(SDL_INIT_VIDEO); 86 | 87 | SDL_Window *window = SDL_CreateWindow( 88 | "RasterizerTest", 89 | SDL_WINDOWPOS_UNDEFINED, 90 | SDL_WINDOWPOS_UNDEFINED, 91 | 640, 92 | 480, 93 | 0 94 | ); 95 | 96 | SDL_Surface *screen = SDL_GetWindowSurface(window); 97 | srand(1234); 98 | 99 | drawTriangle(screen); 100 | 101 | SDL_UpdateWindowSurface(window); 102 | 103 | SDL_Event e; 104 | while (SDL_WaitEvent(&e) && e.type != SDL_QUIT); 105 | 106 | SDL_DestroyWindow(window); 107 | SDL_Quit(); 108 | 109 | return 0; 110 | } -------------------------------------------------------------------------------- /src/examples/Texture.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "SDL.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace swr { 10 | 11 | class Texture { 12 | public: 13 | static constexpr int MAX_ANISOTROPY = 16; 14 | 15 | Texture(SDL_Surface* baseSurface, int maxAnisotropy = 8) 16 | : m_maxAnisotropy(std::clamp(maxAnisotropy, 1, MAX_ANISOTROPY)) 17 | { 18 | if (!baseSurface) { 19 | throw std::runtime_error("Null base surface provided to Texture constructor"); 20 | } 21 | if (!baseSurface->pixels) { 22 | throw std::runtime_error("Base surface has no pixel data"); 23 | } 24 | generateMipmaps(baseSurface); 25 | } 26 | 27 | ~Texture() { 28 | cleanup(); 29 | } 30 | 31 | // Prevent copying since we manage SDL resources 32 | Texture(const Texture&) = delete; 33 | Texture& operator=(const Texture&) = delete; 34 | 35 | void sample(float u, float v, float dudx, float dvdx, float dudy, float dvdy, Uint32& outColor) const { 36 | if (m_mipmaps.empty()) { 37 | outColor = 0; 38 | return; 39 | } 40 | 41 | // Safely wrap texture coordinates 42 | u = std::fmod(u, 1.0f); 43 | v = std::fmod(v, 1.0f); 44 | if (u < 0) u += 1.0f; 45 | if (v < 0) v += 1.0f; 46 | 47 | SDL_Surface* baseLevel = m_mipmaps[0]; 48 | if (!baseLevel || !baseLevel->pixels) { 49 | outColor = 0; 50 | return; 51 | } 52 | 53 | // Compute ellipse axes 54 | float dudx_scaled = dudx * baseLevel->w; 55 | float dvdx_scaled = dvdx * baseLevel->h; 56 | float dudy_scaled = dudy * baseLevel->w; 57 | float dvdy_scaled = dvdy * baseLevel->h; 58 | 59 | float dx_len = std::sqrt(dudx_scaled * dudx_scaled + dvdx_scaled * dvdx_scaled); 60 | float dy_len = std::sqrt(dudy_scaled * dudy_scaled + dvdy_scaled * dvdy_scaled); 61 | 62 | dx_len = std::max(dx_len, 1e-6f); 63 | dy_len = std::max(dy_len, 1e-6f); 64 | 65 | float major_len = std::max(dx_len, dy_len); 66 | float minor_len = std::min(dx_len, dy_len); 67 | 68 | float ratio = std::min(major_len / minor_len, static_cast(m_maxAnisotropy)); 69 | int num_samples = std::max(1, static_cast(std::ceil(ratio))); 70 | 71 | try { 72 | if (num_samples <= 1) { 73 | // Use regular trilinear filtering for low anisotropy 74 | sampleTrilinear(u, v, major_len, outColor); 75 | return; 76 | } 77 | 78 | // Determine major axis direction 79 | float major_du, major_dv; 80 | if (dx_len > dy_len) { 81 | major_du = dudx / dx_len; 82 | major_dv = dvdx / dx_len; 83 | } else { 84 | major_du = dudy / dy_len; 85 | major_dv = dvdy / dy_len; 86 | } 87 | 88 | // Accumulate color components 89 | float r = 0, g = 0, b = 0; 90 | float step = 1.0f / num_samples; 91 | 92 | for (int i = 0; i < num_samples; ++i) { 93 | float t = (i + 0.5f) * step - 0.5f; 94 | float sample_u = u + major_du * major_len * t; 95 | float sample_v = v + major_dv * major_len * t; 96 | 97 | sample_u = std::fmod(sample_u, 1.0f); 98 | sample_v = std::fmod(sample_v, 1.0f); 99 | if (sample_u < 0) sample_u += 1.0f; 100 | if (sample_v < 0) sample_v += 1.0f; 101 | 102 | Uint32 sample_color; 103 | sampleTrilinear(sample_u, sample_v, minor_len, sample_color); 104 | 105 | r += getR(sample_color); 106 | g += getG(sample_color); 107 | b += getB(sample_color); 108 | } 109 | 110 | r = std::min(255.0f, r / num_samples); 111 | g = std::min(255.0f, g / num_samples); 112 | b = std::min(255.0f, b / num_samples); 113 | 114 | outColor = packRGB(static_cast(r), static_cast(g), static_cast(b)); 115 | } catch (const std::exception& e) { 116 | std::cerr << "Exception in sample(): " << e.what() << std::endl; 117 | outColor = 0; 118 | } 119 | } 120 | 121 | private: 122 | void sampleTrilinear(float u, float v, float rho, Uint32& outColor) const { 123 | if (m_mipmaps.empty()) { 124 | outColor = 0; 125 | return; 126 | } 127 | 128 | // Calculate mipmap level based on rho (pixel footprint) 129 | float lod = std::log2(std::max(rho, 1e-6f)); 130 | lod = std::clamp(lod, 0.0f, static_cast(m_mipmaps.size() - 1)); 131 | 132 | int lodBase = static_cast(std::floor(lod)); 133 | int lodNext = std::min(lodBase + 1, static_cast(m_mipmaps.size()) - 1); 134 | float lodFrac = std::clamp(lod - lodBase, 0.0f, 1.0f); 135 | 136 | Uint32 colorBase, colorNext; 137 | sampleBilinear(lodBase, u, v, colorBase); 138 | if (lodBase != lodNext) { 139 | sampleBilinear(lodNext, u, v, colorNext); 140 | outColor = lerpColors(colorBase, colorNext, lodFrac); 141 | } else { 142 | outColor = colorBase; 143 | } 144 | } 145 | 146 | void sampleBilinear(int mipLevel, float u, float v, Uint32& outColor) const { 147 | if (mipLevel < 0 || mipLevel >= m_mipmaps.size()) { 148 | outColor = 0; 149 | return; 150 | } 151 | 152 | SDL_Surface* mip = m_mipmaps[mipLevel]; 153 | if (!mip || !mip->pixels) { 154 | outColor = 0; 155 | return; 156 | } 157 | 158 | float px = u * (mip->w - 1); 159 | float py = v * (mip->h - 1); 160 | 161 | int x0 = std::max(0, int(std::floor(px))); 162 | int y0 = std::max(0, int(std::floor(py))); 163 | int x1 = std::min(mip->w - 1, x0 + 1); 164 | int y1 = std::min(mip->h - 1, y0 + 1); 165 | 166 | float fx = px - x0; 167 | float fy = py - y0; 168 | 169 | Uint32* pixels = static_cast(mip->pixels); 170 | if (!pixels) { 171 | outColor = 0; 172 | return; 173 | } 174 | 175 | Uint32 c00 = pixels[y0 * mip->w + x0]; 176 | Uint32 c10 = pixels[y0 * mip->w + x1]; 177 | Uint32 c01 = pixels[y1 * mip->w + x0]; 178 | Uint32 c11 = pixels[y1 * mip->w + x1]; 179 | 180 | outColor = bilinearLerpColors(c00, c10, c01, c11, fx, fy); 181 | } 182 | 183 | static inline Uint8 getR(Uint32 color) { return (color >> 16) & 0xFF; } 184 | static inline Uint8 getG(Uint32 color) { return (color >> 8) & 0xFF; } 185 | static inline Uint8 getB(Uint32 color) { return color & 0xFF; } 186 | 187 | static inline Uint32 packRGB(Uint8 r, Uint8 g, Uint8 b) { 188 | return (r << 16) | (g << 8) | b; 189 | } 190 | 191 | static inline Uint32 lerpColors(Uint32 c1, Uint32 c2, float t) { 192 | Uint8 r = getR(c1) + (Uint8)((getR(c2) - getR(c1)) * t); 193 | Uint8 g = getG(c1) + (Uint8)((getG(c2) - getG(c1)) * t); 194 | Uint8 b = getB(c1) + (Uint8)((getB(c2) - getB(c1)) * t); 195 | return packRGB(r, g, b); 196 | } 197 | 198 | static inline Uint32 bilinearLerpColors(Uint32 c00, Uint32 c10, Uint32 c01, Uint32 c11, float fx, float fy) { 199 | Uint8 r = (Uint8)( 200 | getR(c00) * (1 - fx) * (1 - fy) + 201 | getR(c10) * fx * (1 - fy) + 202 | getR(c01) * (1 - fx) * fy + 203 | getR(c11) * fx * fy 204 | ); 205 | Uint8 g = (Uint8)( 206 | getG(c00) * (1 - fx) * (1 - fy) + 207 | getG(c10) * fx * (1 - fy) + 208 | getG(c01) * (1 - fx) * fy + 209 | getG(c11) * fx * fy 210 | ); 211 | Uint8 b = (Uint8)( 212 | getB(c00) * (1 - fx) * (1 - fy) + 213 | getB(c10) * fx * (1 - fy) + 214 | getB(c01) * (1 - fx) * fy + 215 | getB(c11) * fx * fy 216 | ); 217 | return packRGB(r, g, b); 218 | } 219 | 220 | void generateMipmaps(SDL_Surface* baseTexture) { 221 | cleanup(); 222 | 223 | if (!baseTexture || !baseTexture->pixels) { 224 | throw std::runtime_error("Invalid base texture in generateMipmaps"); 225 | } 226 | 227 | // Copy base texture 228 | SDL_Surface* baseCopy = SDL_CreateRGBSurface(0, baseTexture->w, baseTexture->h, 32, 229 | 0x00FF0000, // R mask 230 | 0x0000FF00, // G mask 231 | 0x000000FF, // B mask 232 | 0x00000000); // A mask 233 | 234 | if (!baseCopy) { 235 | throw std::runtime_error(std::string("Failed to create base texture copy: ") + SDL_GetError()); 236 | } 237 | 238 | SDL_BlitSurface(baseTexture, NULL, baseCopy, NULL); 239 | m_mipmaps.push_back(baseCopy); 240 | 241 | SDL_Surface* prevLevel = baseCopy; 242 | int width = baseTexture->w; 243 | int height = baseTexture->h; 244 | 245 | // Generate mip chain until 1x1 246 | while (width > 1 || height > 1) { 247 | int newWidth = std::max(1, width / 2); 248 | int newHeight = std::max(1, height / 2); 249 | 250 | SDL_Surface* newMip = SDL_CreateRGBSurface(0, newWidth, newHeight, 32, 251 | 0x00FF0000, // R mask 252 | 0x0000FF00, // G mask 253 | 0x000000FF, // B mask 254 | 0x00000000); // A mask 255 | 256 | if (!newMip) { 257 | break; 258 | } 259 | 260 | if (SDL_LockSurface(prevLevel) < 0 || SDL_LockSurface(newMip) < 0) { 261 | SDL_FreeSurface(newMip); 262 | throw std::runtime_error("Failed to lock surfaces for mipmap generation"); 263 | } 264 | 265 | Uint32* srcPixels = static_cast(prevLevel->pixels); 266 | Uint32* dstPixels = static_cast(newMip->pixels); 267 | 268 | for (int y = 0; y < newHeight; y++) { 269 | for (int x = 0; x < newWidth; x++) { 270 | Uint32 p00 = srcPixels[(y*2) * prevLevel->w + (x*2)]; 271 | Uint32 p10 = x*2+1 < prevLevel->w ? srcPixels[(y*2) * prevLevel->w + (x*2+1)] : p00; 272 | Uint32 p01 = y*2+1 < prevLevel->h ? srcPixels[(y*2+1) * prevLevel->w + (x*2)] : p00; 273 | Uint32 p11 = (x*2+1 < prevLevel->w && y*2+1 < prevLevel->h) ? 274 | srcPixels[(y*2+1) * prevLevel->w + (x*2+1)] : p00; 275 | 276 | Uint8 r = (getR(p00) + getR(p10) + getR(p01) + getR(p11)) >> 2; 277 | Uint8 g = (getG(p00) + getG(p10) + getG(p01) + getG(p11)) >> 2; 278 | Uint8 b = (getB(p00) + getB(p10) + getB(p01) + getB(p11)) >> 2; 279 | 280 | dstPixels[y * newMip->w + x] = packRGB(r, g, b); 281 | } 282 | } 283 | 284 | SDL_UnlockSurface(prevLevel); 285 | SDL_UnlockSurface(newMip); 286 | 287 | m_mipmaps.push_back(newMip); 288 | 289 | prevLevel = newMip; 290 | width = newWidth; 291 | height = newHeight; 292 | } 293 | } 294 | 295 | void cleanup() { 296 | for (SDL_Surface* mip : m_mipmaps) { 297 | if (mip) { 298 | SDL_FreeSurface(mip); 299 | } 300 | } 301 | m_mipmaps.clear(); 302 | } 303 | 304 | std::vector m_mipmaps; 305 | int m_maxAnisotropy; 306 | }; 307 | 308 | } // namespace swr -------------------------------------------------------------------------------- /src/examples/TextureExample.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "SDL.h" 4 | #include "Texture.h" 5 | #include 6 | 7 | class TexturedPixelShader : public PixelShaderBase { 8 | public: 9 | static const bool InterpolateZ = false; 10 | static const bool InterpolateW = true; // Required for perspective correct texturing 11 | static const int AVarCount = 0; 12 | static const int PVarCount = 2; // UV coordinates 13 | 14 | static SDL_Surface* surface; 15 | static std::shared_ptr texture; 16 | 17 | static void drawPixel(const PixelData &p) 18 | { 19 | // Compute texture coordinate derivatives 20 | float dudx, dudy, dvdx, dvdy; 21 | p.computePerspectiveDerivatives(*p.equations, 0, dudx, dudy); // U derivatives 22 | p.computePerspectiveDerivatives(*p.equations, 1, dvdx, dvdy); // V derivatives 23 | 24 | Uint32 sampledColor; 25 | texture->sample(p.pvar[0], p.pvar[1], dudx, dvdx, dudy, dvdy, sampledColor); 26 | 27 | Uint32 *screenBuffer = (Uint32*)((Uint8 *)surface->pixels + p.y * surface->pitch + p.x * 4); 28 | *screenBuffer = sampledColor; 29 | } 30 | }; 31 | 32 | // Static member initialization 33 | SDL_Surface* TexturedPixelShader::surface; 34 | std::shared_ptr TexturedPixelShader::texture; -------------------------------------------------------------------------------- /src/examples/VertexProcessorTest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2020 Markus Trenkwalder 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | #include "SDL.h" 26 | #include "Renderer.h" 27 | 28 | using namespace swr; 29 | 30 | class PixelShader : public PixelShaderBase { 31 | public: 32 | static const bool InterpolateZ = false; 33 | static const bool InterpolateW = false; 34 | static const int AVarCount = 3; 35 | static const int PVarCount = 0; 36 | 37 | static SDL_Surface* surface; 38 | 39 | static void drawPixel(const PixelData &p) 40 | { 41 | int rint = (int)(p.avar[0] * 255); 42 | int gint = (int)(p.avar[1] * 255); 43 | int bint = (int)(p.avar[2] * 255); 44 | 45 | Uint32 color = rint << 16 | gint << 8 | bint; 46 | 47 | Uint32 *buffer = (Uint32*)((Uint8 *)surface->pixels + (size_t)p.y * (size_t)surface->pitch + (size_t)p.x * 4); 48 | *buffer = color; 49 | } 50 | }; 51 | 52 | SDL_Surface* PixelShader::surface; 53 | 54 | struct VertexData { 55 | float x, y, z; 56 | float r, g, b; 57 | }; 58 | 59 | class VertexShader : public VertexShaderBase { 60 | public: 61 | static const int AttribCount = 1; 62 | static const int AVarCount = 3; 63 | static const int PVarCount = 0; 64 | 65 | static void processVertex(VertexShaderInput in, VertexShaderOutput *out) 66 | { 67 | const VertexData *data = static_cast(in[0]); 68 | out->x = data->x; 69 | out->y = data->y; 70 | out->z = data->z; 71 | out->w = 1.0f; 72 | out->avar[0] = data->r; 73 | out->avar[1] = data->g; 74 | out->avar[2] = data->b; 75 | } 76 | }; 77 | 78 | void drawTriangles(SDL_Surface *s) 79 | { 80 | Rasterizer r; 81 | VertexProcessor v(&r); 82 | 83 | r.setScissorRect(0, 0, 640, 480); 84 | r.setPixelShader(); 85 | PixelShader::surface = s; 86 | 87 | v.setViewport(100, 100, 640 - 200, 480 - 200); 88 | v.setCullMode(CullMode::None); 89 | v.setVertexShader(); 90 | 91 | VertexData vdata[3]; 92 | 93 | vdata[0].x = 0.0f; 94 | vdata[0].y = 0.5f; 95 | vdata[0].z = 0.0f; 96 | vdata[0].r = 1.0f; 97 | vdata[0].g = 0.0f; 98 | vdata[0].b = 0.0f; 99 | 100 | vdata[1].x = -1.5f; 101 | vdata[1].y = -0.5f; 102 | vdata[1].z = 0.0f; 103 | vdata[1].r = 0.0f; 104 | vdata[1].g = 1.0f; 105 | vdata[1].b = 0.0f; 106 | 107 | vdata[2].x = 1.5f; 108 | vdata[2].y = -0.5f; 109 | vdata[2].z = 0.0f; 110 | vdata[2].r = 0.0f; 111 | vdata[2].g = 0.0f; 112 | vdata[2].b = 1.0f; 113 | 114 | int idata[3]; 115 | idata[0] = 0; 116 | idata[1] = 1; 117 | idata[2] = 2; 118 | 119 | v.setVertexAttribPointer(0, sizeof(VertexData), vdata); 120 | v.drawElements(DrawMode::Triangle, 3, idata); 121 | } 122 | 123 | int main(int argc, char *argv[]) 124 | { 125 | SDL_Init(SDL_INIT_VIDEO); 126 | 127 | SDL_Window *window = SDL_CreateWindow( 128 | "VertexProcessorTest", 129 | SDL_WINDOWPOS_UNDEFINED, 130 | SDL_WINDOWPOS_UNDEFINED, 131 | 640, 132 | 480, 133 | 0 134 | ); 135 | 136 | SDL_Surface *screen = SDL_GetWindowSurface(window); 137 | srand(1234); 138 | 139 | drawTriangles(screen); 140 | 141 | SDL_UpdateWindowSurface(window); 142 | 143 | SDL_Event e; 144 | while (SDL_WaitEvent(&e) && e.type != SDL_QUIT); 145 | 146 | SDL_DestroyWindow(window); 147 | SDL_Quit(); 148 | 149 | return 0; 150 | } -------------------------------------------------------------------------------- /src/examples/vector_math.h: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2020 Markus Trenkwalder 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | #ifndef VECTOR_MATH_H 26 | #define VECTOR_MATH_H 27 | 28 | #include 29 | 30 | #undef minor 31 | 32 | #ifndef M_PI 33 | #define M_PI 3.14159265358979323846 34 | #endif 35 | 36 | namespace vmath { 37 | 38 | template 39 | T rsqrt(T x) 40 | { 41 | return T(1) / sqrt(x); 42 | } 43 | 44 | template 45 | T inv(T x) 46 | { 47 | return T(1) / x; 48 | } 49 | 50 | namespace detail { 51 | // This function is used heavily in this library. Here is a generic 52 | // implementation for it. If you can provide a faster one for your specific 53 | // types this can speed up things considerably. 54 | template 55 | inline T multiply_accumulate(int count, const T *a, const T *b) 56 | { 57 | T result = T(0); 58 | for (int i = 0; i < count; ++i) 59 | result += a[i] * b[i]; 60 | return result; 61 | } 62 | } 63 | 64 | #define MOP_M_CLASS_TEMPLATE(CLASS, OP, COUNT) \ 65 | CLASS & operator OP (const CLASS& rhs) \ 66 | { \ 67 | for (int i = 0; i < (COUNT); ++i ) \ 68 | (*this)[i] OP rhs[i]; \ 69 | return *this; \ 70 | } 71 | 72 | #define MOP_M_TYPE_TEMPLATE(CLASS, OP, COUNT) \ 73 | CLASS & operator OP (const T & rhs) \ 74 | { \ 75 | for (int i = 0; i < (COUNT); ++i ) \ 76 | (*this)[i] OP rhs; \ 77 | return *this; \ 78 | } 79 | 80 | #define MOP_COMP_TEMPLATE(CLASS, COUNT) \ 81 | bool operator == (const CLASS & rhs) \ 82 | { \ 83 | bool result = true; \ 84 | for (int i = 0; i < (COUNT); ++i) \ 85 | result = result && (*this)[i] == rhs[i]; \ 86 | return result; \ 87 | } \ 88 | bool operator != (const CLASS & rhs) \ 89 | { return !((*this) == rhs); } 90 | 91 | #define MOP_G_UMINUS_TEMPLATE(CLASS, COUNT) \ 92 | CLASS operator - () const \ 93 | { \ 94 | CLASS result; \ 95 | for (int i = 0; i < (COUNT); ++i) \ 96 | result[i] = -(*this)[i]; \ 97 | return result; \ 98 | } 99 | 100 | #define COMMON_OPERATORS(CLASS, COUNT) \ 101 | MOP_M_CLASS_TEMPLATE(CLASS, +=, COUNT) \ 102 | MOP_M_CLASS_TEMPLATE(CLASS, -=, COUNT) \ 103 | /*no *= as this is not the same for vectors and matrices */ \ 104 | MOP_M_CLASS_TEMPLATE(CLASS, /=, COUNT) \ 105 | MOP_M_TYPE_TEMPLATE(CLASS, +=, COUNT) \ 106 | MOP_M_TYPE_TEMPLATE(CLASS, -=, COUNT) \ 107 | MOP_M_TYPE_TEMPLATE(CLASS, *=, COUNT) \ 108 | MOP_M_TYPE_TEMPLATE(CLASS, /=, COUNT) \ 109 | MOP_G_UMINUS_TEMPLATE(CLASS, COUNT) \ 110 | MOP_COMP_TEMPLATE(CLASS, COUNT) 111 | 112 | #define VECTOR_COMMON(CLASS, COUNT) \ 113 | COMMON_OPERATORS(CLASS, COUNT) \ 114 | MOP_M_CLASS_TEMPLATE(CLASS, *=, COUNT) \ 115 | operator const T* () const { return &x; } \ 116 | operator T* () { return &x; } 117 | 118 | #define FOP_G_SOURCE_TEMPLATE(OP) \ 119 | { C r = lhs; r OP##= rhs; return r; } 120 | 121 | #define FOP_G_CLASS_TEMPLATE(OP) \ 122 | template