From 5cb5c19f9ac2f5da3c9cb3086c270113e5ce93ff Mon Sep 17 00:00:00 2001 From: Cakez77 Date: Thu, 19 Oct 2023 14:29:23 +0200 Subject: [PATCH] Mac Platform Integration changes for Schnitzel Motor Engine: - Extend build.sh to include Homebrew checks and GLFW, Freetype library paths on Mac. - Remove old object files and build files during the build process. - Add new quad shaders for OpenGL 4.1. - Introduce separate README for Mac-specific instructions. - Remove Linux-specific platform code in favor of a more unified approach. - Refactor Mac platform integration, remove Objective-C dependency. - Update README and .clang-format for better documentation and code style. - Add .gitattributes for consistent Git configuration. - Explicitly set C++ version requirement to c++14 renderer.cpp changes: - Preprocessor Directives: Added #define USE_OPENGL410 to toggle between OpenGL 4.10 and earlier versions. - Includes: Reordered includes for better organization. - OpenGL Structs: Added #ifdef USE_OPENGL410 to include new IDs (vaoID, vboID) for OpenGL 4.10. - Font Loading: Simplified assignments for glyph structures. - Debug Callback: Wrapped the existing debug callback function with #ifndef __APPLE__. - Shader Creation: Conditional compilation for shader source based on USE_OPENGL410. - Link Errors: Added a function check_link_errors to check for shader program linking errors. - Vertex Buffer Allocation: Added a new function create_vertex_array_buffer to create vertex array buffers. - Initialize Vertex Array Buffers: Added a new function init_vertex_array_buffers to initialize vertex array and storage buffers conditionally based on USE_OPENGL410. - OpenGL Initialization: Modified to use new functions for error checking and buffer initialization. - Rendering: Modified to use VAO and VBO when USE_OPENGL410 is defined. --- .clang-format | 35 +++ .gitattributes | 1 + .github/workflows/mac.yml | 19 ++ .gitignore | 1 + MAC_README.md | 66 ++++++ README.md | 52 ++++- assets/shaders/quad.vert | 3 +- assets/shaders/quad_41.frag | 35 +++ assets/shaders/quad_41.vert | 58 +++++ build.sh | 31 ++- src/gl_renderer.cpp | 436 ++++++++++++++++++++++-------------- src/gl_renderer.h | 8 +- src/input.h | 4 +- src/mac_platform.cpp | 334 ++++++++++++++++++++++++++- src/mac_platform.m | 56 ----- src/main.cpp | 5 +- src/schnitzel_lib.h | 6 +- src/ui.h | 34 ++- 18 files changed, 899 insertions(+), 285 deletions(-) create mode 100644 .clang-format create mode 100644 .gitattributes create mode 100644 .github/workflows/mac.yml create mode 100644 MAC_README.md create mode 100644 assets/shaders/quad_41.frag create mode 100644 assets/shaders/quad_41.vert delete mode 100644 src/mac_platform.m diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..5c3a9d2 --- /dev/null +++ b/.clang-format @@ -0,0 +1,35 @@ +--- +ColumnLimit: 90 +BreakBeforeBinaryOperators: false +ReflowComments: true +BasedOnStyle: LLVM +IndentWidth: 2 +UseTab: false + +IndentCaseLabels: true +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: true + AfterControlStatement: Always + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +AllowShortBlocksOnASingleLine: Never +MaxEmptyLinesToKeep: 1 + +PointerAlignment: Left +AllowShortFunctionsOnASingleLine: None +BinPackParameters: true +BinPackArguments: true +AlignAfterOpenBracket: Align +BreakConstructorInitializers: BeforeColon + +--- diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2125666 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto \ No newline at end of file diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml new file mode 100644 index 0000000..43e1e02 --- /dev/null +++ b/.github/workflows/mac.yml @@ -0,0 +1,19 @@ +name: mac +on: [push, pull_request] + +jobs: + build: + runs-on: macos-latest + env: + CC: clang + CXX: clang++ + flags: "-g -std=c++14" + steps: + - name: install dependencies + run: | + brew update + brew install glfw3 freetype + - name: checkout + uses: actions/checkout@v3 + - name: build + run: ./build.sh \ No newline at end of file diff --git a/.gitignore b/.gitignore index d7c0297..e7995d8 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ build .DS_Store *.swp *.dSYM/ +*.dylib *.so *.xcuserstate *.xcworkspace diff --git a/MAC_README.md b/MAC_README.md new file mode 100644 index 0000000..423fccb --- /dev/null +++ b/MAC_README.md @@ -0,0 +1,66 @@ +# Schnitzel Motor Engine: Mac Build Notes + +The goal in bring the Schnitzel Motor Engine to the Mac platform was to provide a seamless and consistent integration with the supported platforms, focusing on simplicity and foundational elements that make this engine a terrific choice for students learning to write games in C++ for the first time. This document details the technical considerations, challenges, and rationale we encountered and the solution we settled upon. + +## OpenGL Support on macOS + +The Schnitzel Motor Engine utilizes OpenGL 4.1, supported by Apple up to macOS 10.11 Mojave. Key factors influenced this choice: + +- **Ease of Adoption**: OpenGL 4.1 is native to macOS from version 10.11 Mojave onwards, eliminating additional software installations. Including necessary headers and libraries is straightforward. +- **Portability**: Transitioning the OpenGL Schnitzel Motor Engine to macOS necessitates minimal code alterations, ensuring efficiency. +- **Beginner-Friendly**: OpenGL is an intuitive graphics API, suitable for those new to game development. + +However, there are limitations: +- **Deprecation**: OpenGL 4.1 is deprecated, risking removal in future macOS versions. +- **Apple's Metal Recommendation**: Apple advocates Metal for graphics rendering on macOS and iOS, hinting at a potential phase-out of OpenGL support. + +## OpenGL Implementation Challenges on Mac + +Support for OpenGL 4.3 and above is absent on macOS, requiring reliance on OpenGL 4.1. This alignment allows progression with minimal codebase adjustments, albeit with certain missing features: + +- **Shader Storage Buffer Objects (SSBOs)**: Enable shaders to read from and write to buffer objects. +- **Debug Output**: Furnishes debugging and performance information from the OpenGL driver. +- **Texture Views**: Permit creating multiple views of a texture. +- **Vertex Attribute Binding**: Manages binding between vertex attributes and buffer objects. + +These missing features necessitate alternative approaches or extensions for robust engine operation on OpenGL 4.1. + +## Shader Adaptation from OpenGL 4.3 to 4.1 + +Transitioning the OpenGL 4.3 shader code to comply with OpenGL 4.1 standards posed challenges due to certain missing features. Various strategies ensured a successful transition with performance remaining close to the original version. + +### 1. **Buffer Management**: + - The `USE_OPENGL410` directive segregates buffer data handling for OpenGL 4.1 and 4.3. + - For OpenGL 4.1: + - A Vertex Buffer Object (VBO) stores vertex data, with `glBufferData` allocating buffer space, and `glVertexAttribPointer` setting up attribute pointers for interleaved data. + - For OpenGL 4.3: + - A Shader Storage Buffer Object (SSBO) manages transform data, a feature absent in OpenGL 4.1, requiring a different buffer management strategy. + +### **Key Differences**: + - **Buffer Management**: Use of VBOs in OpenGL 4.1 versus SSBOs in OpenGL 4.3 for handling buffer data. + - **Vertex Processing**: Explicit vertex data processing in OpenGL 4.1 compared to the more streamlined approach in OpenGL 4.3. + - **Shader Compilation**: Consistent shader compilation and error handling across both versions aids in debugging and ensures shader correctness. + +## Solution + +We opted for OpenGL as it aligns with the graphics API used on Windows and Linux, despite certain caveats on Mac due to its limitation to OpenGL 4.1 and the API's deprecation on this platform. + +### **Pros**: + - **Performance**: Employed strategies upheld performance despite OpenGL 4.1 restrictions. + - **Learning Opportunity**: Porting shader code across OpenGL versions enlightens on the API's evolution. + +Due to the absence of SSBOs in OpenGL 4.1, we opted for Vertex Buffer Objects (VBO) due to their speed, performance, simplicity, and alignment with our educational goals. VBOs, present since OpenGL 1.5, cater to our engine's needs efficiently. While the Schnitzel Motor Engine minimizes third-party library dependencies, we employed GLFW for its robust window management and streamlined OpenGL setup. GLFW simplifies build configurations, enabling a consistent build process across macOS, Windows, and Linux using the same "Single Compilation Unit" (SCU). + +## Summary + +The utilization of OpenGL 4.1 for the Schnitzel Motor Engine on macOS is driven by its ease of adoption, portability, and beginner-friendly nature, aligning with our educational goals. The deprecation of OpenGL 4.1 and the absence of certain features necessitate alternative strategies for robust engine operation. The use of GLFW enables a streamlined OpenGL setup on macOS, aligning with the engine's design principle of minimizing third-party dependencies. + +Looking ahead, the aging OpenGL 4.1 may necessitate exploring other graphics APIs or projects to ensure the engine's longevity and adaptability to modern graphics programming standards. + +### Resources +- [OpenGL Official Website](https://www.opengl.org/) +- [GLFW Official Website](https://www.glfw.org/) +- [Vulkan Official Website](https://www.khronos.org/vulkan/) +- [Metal - Apple Developer](https://developer.apple.com/metal/) +- [MGL Project on GitHub](https://github.com/openglonmetal/MGL) +- [Shader Storage Buffer Object - OpenGL Wiki](https://www.khronos.org/opengl/wiki/Shader_Storage_Buffer_Object) diff --git a/README.md b/README.md index bb2fa84..1b0e1aa 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,51 @@ # SchnitzelMotor -A crispy cross platform C/C++ engine. Supports Linux and Windows currently. +A crispy cross platform C/C++ engine. Supports Linux, Windows, and macOS. -# Building the engine (Windows) -1. Download and install clang from here https://github.com/llvm/llvm-project/releases/download/llvmorg-16.0.6/LLVM-16.0.6-win64.exe +# Building the Engine + +## Windows Instructions: +1. Download and install clang from [here](https://github.com/llvm/llvm-project/releases/download/llvmorg-16.0.6/LLVM-16.0.6-win64.exe) 2. ![image](https://github.com/Cakez77/SchnitzelMotor/assets/45374095/1ad4bdf5-f43c-4774-9f34-5fcdd2ed7f2c) -3. Download and install git from here +3. Download and install git from [here](https://git-scm.com/download/win) 4. Add `sh.exe` to the path, you can find it here : `C:\Program Files\Git\bin` -5. + +## Mac Instructions: +1. Ensure you have [Homebrew](https://brew.sh/) installed on your machine. + ```bash + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + ``` +2. Install dependencies using Homebrew: + ```bash + brew update + brew install llvm glfw freetype + ``` +3. Clone the repository: + ```bash + git clone https://github.com/Cakez77/SchnitzelMotor.git + cd SchnitzelMotor + ``` +4. Run the build script: + ```bash + ./build.sh + ``` + +## GitHub Actions (macOS) +For automated builds on macOS using GitHub Actions, the provided `mac.yml` workflow file can be utilized. This workflow installs necessary dependencies, checks out the code, and runs the build script: +```yaml +name: mac +on: [push, pull_request] + +jobs: + build: + runs-on: macos-latest + steps: + - name: install dependencies + run: | + brew update + brew install glfw3 freetype + - name: checkout + uses: actions/checkout@v3 + - name: build + run: ./build.sh +``` +For more information on GitHub Actions, refer to the [official documentation](https://docs.github.com/en/actions). diff --git a/assets/shaders/quad.vert b/assets/shaders/quad.vert index 6ea5e63..4a94f2f 100644 --- a/assets/shaders/quad.vert +++ b/assets/shaders/quad.vert @@ -1,7 +1,6 @@ // Input Uniforms uniform vec2 screenSize; -uniform vec2 cameraPos; uniform mat4 orthoProjection; // Input Buffers @@ -12,7 +11,7 @@ layout(std430, binding = 0) buffer TransformSBO // Output layout (location = 0) out vec2 textureCoordsOut; -layout (location = 1) out int renderOptionsOut; +layout (location = 1) out flat int renderOptionsOut; void main() { diff --git a/assets/shaders/quad_41.frag b/assets/shaders/quad_41.frag new file mode 100644 index 0000000..4cfa0f1 --- /dev/null +++ b/assets/shaders/quad_41.frag @@ -0,0 +1,35 @@ +// Input +layout (location = 0) in vec2 textureCoords; +layout (location = 1) flat in int renderOptions; + +// Output +layout (location = 0) out vec4 fragColor; + +// Uniforms (no binding) +uniform sampler2D textureAtlas; +uniform sampler2D fontAtlas; + +void main() +{ + vec4 textureColor; + + if(bool(renderOptions & RENDERING_OPTION_FONT)) + { + textureColor = texelFetch(fontAtlas, ivec2(textureCoords), 0); + if(textureColor.r == 0.0) + { + discard; + } + textureColor = vec4(1.0, 1.0, 1.0, 1.0); // Filling with white + } + else + { + textureColor = texelFetch(textureAtlas, ivec2(textureCoords), 0); + if(textureColor.a == 0.0) + { + discard; + } + } + + fragColor = textureColor; +} diff --git a/assets/shaders/quad_41.vert b/assets/shaders/quad_41.vert new file mode 100644 index 0000000..03a955e --- /dev/null +++ b/assets/shaders/quad_41.vert @@ -0,0 +1,58 @@ +// Input Uniforms +uniform vec2 screenSize; +uniform mat4 orthoProjection; + +// Input Attributes from VBO +layout (location = 0) in vec2 positionIn; +layout (location = 1) in vec2 sizeIn; +layout (location = 2) in vec2 atlasOffsetIn; +layout (location = 3) in vec2 spriteSizeIn; +layout (location = 4) in int renderOptionsIn; +layout (location = 5) in float layerIn; + +// Output +layout (location = 0) out vec2 textureCoords; +layout (location = 1) flat out int renderOptions; + +void main() +{ + // Creating Vertices on the GPU (2D Engine) + // OpenGL Device Coordinates + // -1 / 1 1 / 1 + // -1 /-1 1 /-1 + vec2 vertices[6]; + vertices[0] = positionIn; + vertices[1] = positionIn + vec2(0.0, sizeIn.y); + vertices[2] = positionIn + vec2(sizeIn.x, 0.0); + vertices[3] = positionIn + vec2(sizeIn.x, 0.0); + vertices[4] = positionIn + vec2(0.0, sizeIn.y); + vertices[5] = positionIn + sizeIn; + + // Initialize textureCoords + float left = atlasOffsetIn.x; + float top = atlasOffsetIn.y; + float right = atlasOffsetIn.x + spriteSizeIn.x; + float bottom = atlasOffsetIn.y + spriteSizeIn.y; + + if (bool(renderOptionsIn & RENDERING_OPTION_FLIP_X)) + { + float tmpLeft = left; + left = right; + right = tmpLeft; + } + + vec2 localTextureCoords[6]; + localTextureCoords[0] = vec2(left, top); + localTextureCoords[1] = vec2(left, bottom); + localTextureCoords[2] = vec2(right, top); + localTextureCoords[3] = vec2(right, top); + localTextureCoords[4] = vec2(left, bottom); + localTextureCoords[5] = vec2(right, bottom); + + // Compute final positions and outputs + vec2 vertexPos = vertices[gl_VertexID]; + gl_Position = orthoProjection * vec4(vertexPos, layerIn, 1.0); + + textureCoords = localTextureCoords[gl_VertexID]; + renderOptions = renderOptionsIn; +} diff --git a/build.sh b/build.sh index 2a148bd..b6ccd3d 100755 --- a/build.sh +++ b/build.sh @@ -4,6 +4,7 @@ defines="-DENGINE" warnings="-Wno-writable-strings -Wno-format-security -Wno-c++11-extensions -Wno-deprecated-declarations" includes="-Ithird_party" timestamp=$(date +%s) +flags="${flags:--g}" if [[ "$(uname)" == "Linux" ]]; then echo "Running on Linux" @@ -13,18 +14,29 @@ if [[ "$(uname)" == "Linux" ]]; then # fPIC position independent code rm -f game_* # Remove old game_* files - clang++ -g "src/game.cpp" -shared -fPIC -o game_$timestamp.so $warnings $defines + clang++ $flags "src/game.cpp" -shared -fPIC -o game_$timestamp.so $warnings $defines mv game_$timestamp.so game.so elif [[ "$(uname)" == "Darwin" ]]; then echo "Running on Mac" - libs="-framework Cocoa" - sdkpath=$(xcode-select --print-path)/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk - includes="-Ithird_party -isysroot ${sdkpath} -I${sdkpath}/System/Library/Frameworks/Cocoa.framework/Headers" - objc_dep="src/mac_platform.m" + + # Check if Homebrew is installed + if ! command -v brew &> /dev/null; then + echo "Homebrew not found. Please install Homebrew and try again." + exit 1 + fi + + # Build Game Library + rm -f -R *.dylib *.dSYM # Remove old build files + clang++ $flags -dynamiclib "src/game.cpp" -o game_$timestamp.dylib $warnings $defines + mv game_$timestamp.dylib game.dylib + mv game_$timestamp.dylib.dSYM game.dylib.dSYM + + # Game Engine Compiler Settings on Mac + HOMEBREW_CELLAR=${HOMEBREW_CELLAR:-/usr/local/Cellar} # Homebrew defaults to /usr/local/Cellar + libs="-framework Cocoa -framework OpenGL -L${HOMEBREW_CELLAR}/glfw/3.3.8/lib -lglfw -L${HOMEBREW_CELLAR}/freetype/2.13.2/lib -lfreetype" + includes="-Ithird_party -I${HOMEBREW_CELLAR}/glfw/3.3.8/include -I${HOMEBREW_CELLAR}/freetype/2.13.2/include/freetype2" outputFile=schnitzel - # clean up old object files - rm -f src/*.o else echo "Not running on Linux" libs="-luser32 -lgdi32 -lopengl32 -lole32 -Lthird_party/lib -lfreetype.lib" @@ -32,7 +44,7 @@ else queryProcesses=$(tasklist | grep $outputFile) rm -f game_* # Remove old game_* files - clang++ -g "src/game.cpp" -shared -o game_$timestamp.dll $warnings $defines + clang++ $flags "src/game.cpp" -shared -o game_$timestamp.dll $warnings $defines mv game_$timestamp.dll game.dll fi @@ -40,8 +52,7 @@ processRunning=$queryProcesses if [ -z "$processRunning" ]; then echo "Engine not running, building main..." - clang++ $includes -g "src/main.cpp" $objc_dep -o $outputFile $libs $warnings $defines + clang++ $includes $flags "src/main.cpp" -o $outputFile $libs $warnings $defines else echo "Engine running, not building!" fi - diff --git a/src/gl_renderer.cpp b/src/gl_renderer.cpp index 656f7ce..9b79a28 100644 --- a/src/gl_renderer.cpp +++ b/src/gl_renderer.cpp @@ -14,7 +14,7 @@ // OpenGL Constants // ############################################################################# const char* TEXTURE_PATH = "assets/textures/Texture_Atlas_01.png"; - +const int ERROR_LOG_SIZE = 2048; // ############################################################################# // OpenGL Structs @@ -22,7 +22,12 @@ const char* TEXTURE_PATH = "assets/textures/Texture_Atlas_01.png"; struct GLContext { int programID; - int transformSBOID; +#ifdef USE_OPENGL410 + int vaoID; // Vertex Array Object + int vboID; // Vertex Buffer Object +#else + int transformSBOID; // Shader Storage Buffer Object +#endif int screenSizeID; int projectionID; int textureID; @@ -34,6 +39,7 @@ struct GLContext // OpenGL Globals // ############################################################################# static GLContext glContext; +static char error_log[ERROR_LOG_SIZE] = {}; // ############################################################################# // Render Interface Implementations @@ -52,7 +58,7 @@ void load_font(char* filePath, int fontSize) int col = padding; const int textureWidth = 512; - char textureBuffer[textureWidth * textureWidth]; + char textureBuffer[textureWidth * textureWidth]; for (FT_ULong glyphIdx = 32; glyphIdx < 127; ++glyphIdx) { FT_UInt glyphIndex = FT_Get_Char_Index(fontFace, glyphIdx); @@ -73,9 +79,9 @@ void load_font(char* filePath, int fontSize) fontFace->glyph->bitmap.buffer[y * fontFace->glyph->bitmap.width + x]; } } - + // clang-format off Glyph* glyph = &renderData->glyphs[glyphIdx]; - glyph->textureCoords = + glyph->textureCoords = { (float)col, (float)row @@ -95,7 +101,7 @@ void load_font(char* filePath, int fontSize) (float)fontFace->glyph->bitmap_left, (float)fontFace->glyph->bitmap_top, }; - + // clang-format on col += fontFace->glyph->bitmap.width + padding; } @@ -108,8 +114,8 @@ void load_font(char* filePath, int fontSize) glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, glContext.fontAtlasID); - glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, textureWidth, textureWidth, 0, - GL_RED, GL_UNSIGNED_BYTE, (char*)textureBuffer); + glTexImage2D( + GL_TEXTURE_2D, 0, GL_R8, textureWidth, textureWidth, 0, GL_RED, GL_UNSIGNED_BYTE, (char*)textureBuffer); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); @@ -121,12 +127,15 @@ void load_font(char* filePath, int fontSize) // ############################################################################# // OpenGL Functions // ############################################################################# -static void APIENTRY gl_debug_callback(GLenum source, GLenum type, GLuint id, GLenum severity, - GLsizei length, const GLchar* message, const void* user) + +// Debug callback is not supported in OpenGL 4.1 +#ifndef USE_OPENGL410 +static void APIENTRY gl_debug_callback( + GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* user) { - if(severity == GL_DEBUG_SEVERITY_LOW || - severity == GL_DEBUG_SEVERITY_MEDIUM || - severity == GL_DEBUG_SEVERITY_HIGH) + if (severity == GL_DEBUG_SEVERITY_LOW || + severity == GL_DEBUG_SEVERITY_MEDIUM || + severity == GL_DEBUG_SEVERITY_HIGH) { SM_ASSERT(0, "OpenGL Error: %s", message); } @@ -135,44 +144,53 @@ static void APIENTRY gl_debug_callback(GLenum source, GLenum type, GLuint id, GL SM_TRACE((char*)message); } } +#endif + +void get_shader_info_log(GLuint shader) +{ + GLsizei log_length = 0; + glGetShaderInfoLog(shader, ERROR_LOG_SIZE, &log_length, error_log); + if (log_length > 0) + { + // Handle or output the error log as needed + SM_ASSERT(false, "Shader compilation failed, Error: %s", error_log); + } +} GLuint gl_create_shader(int shaderType, char* shaderPath, BumpAllocator* transientStorage) { int fileSize = 0; char* shaderHeader = read_file("src/shader_header.h", &fileSize, transientStorage); char* shaderSource = read_file(shaderPath, &fileSize, transientStorage); - if(!shaderHeader) + if (!shaderHeader) { SM_ASSERT(false, "Failed to load shader_header.h"); return 0; } - if(!shaderSource) + if (!shaderSource) { - SM_ASSERT(false, "Failed to load shader: %s",shaderPath); + SM_ASSERT(false, "Failed to load shader: %s", shaderPath); return 0; } - char* shaderSources[] = - { - "#version 430 core\r\n", - shaderHeader, - shaderSource - }; - +#ifdef USE_OPENGL410 + char* shaderSources[] = {"#version 410 core\r\n", shaderHeader, shaderSource}; +#else + char* shaderSources[] = {"#version 430 core\r\n", shaderHeader, shaderSource}; +#endif GLuint shaderID = glCreateShader(shaderType); glShaderSource(shaderID, ArraySize(shaderSources), shaderSources, 0); + glCompileShader(shaderID); - // Test if Shader compiled successfully + // Test if Shader compiled successfully { int success; - char shaderLog[2048] = {}; glGetShaderiv(shaderID, GL_COMPILE_STATUS, &success); - if(!success) + if (!success) { - glGetShaderInfoLog(shaderID, 2048, 0, shaderLog); - SM_ASSERT(false, "Failed to compile %s Shader, Error: %s", shaderPath, shaderLog); + get_shader_info_log(shaderID); return 0; } } @@ -180,51 +198,117 @@ GLuint gl_create_shader(int shaderType, char* shaderPath, BumpAllocator* transie return shaderID; } -bool gl_init(BumpAllocator* transientStorage) +// ############################################################################# +// OpenGL Initialization +// ############################################################################# +const char* get_gl_error_string(GLenum error) { - load_gl_functions(); + switch (error) + { + case GL_NO_ERROR: + return "No error"; + case GL_INVALID_ENUM: + return "Invalid enum"; + case GL_INVALID_VALUE: + return "Invalid value"; + case GL_INVALID_OPERATION: + return "Invalid operation"; + case GL_STACK_OVERFLOW: + return "Stack overflow"; + case GL_STACK_UNDERFLOW: + return "Stack underflow"; + case GL_OUT_OF_MEMORY: + return "Out of memory"; + case GL_INVALID_FRAMEBUFFER_OPERATION: + return "Invalid framebuffer operation"; + default: + return "Unknown error"; + } +} - glDebugMessageCallback(&gl_debug_callback, 0); - glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); - glEnable(GL_DEBUG_OUTPUT); - GLuint vertShaderID = gl_create_shader(GL_VERTEX_SHADER, - "assets/shaders/quad.vert", transientStorage); - GLuint fragShaderID = gl_create_shader(GL_FRAGMENT_SHADER, - "assets/shaders/quad.frag", transientStorage); - if(!vertShaderID || !fragShaderID) +void check_link_errors(GLuint programID) +{ + GLint success; + glGetProgramiv(programID, GL_LINK_STATUS, &success); + if (!success) { - SM_ASSERT(false, "Failed to create Shaders") - return false; + GLint maxLength = 0; + glGetProgramiv(programID, GL_INFO_LOG_LENGTH, &maxLength); + maxLength = min(maxLength, ERROR_LOG_SIZE); + glGetProgramInfoLog(programID, maxLength, &maxLength, error_log); + glDeleteProgram(programID); // program is useless now + SM_ASSERT(false, "Shader Linking failed, Error: %s", error_log); } +} - glContext.programID = glCreateProgram(); - glAttachShader(glContext.programID, vertShaderID); - glAttachShader(glContext.programID, fragShaderID); - glLinkProgram(glContext.programID); +#ifdef USE_OPENGL410 +// Create a Vertex Array Object (VAO) and a Vertex Buffer Object (VBO) +void create_vertex_array_buffers() +{ + // clear errors + while (glGetError() != GL_NO_ERROR) + ; - // Validate if program works - { - int programSuccess; - char programInfoLog[512]; - glGetProgramiv(glContext.programID, GL_LINK_STATUS, &programSuccess); + // Initialize OpenGL buffers + glGenVertexArrays(1, (GLuint*)&glContext.vaoID); + glBindVertexArray(glContext.vaoID); - if(!programSuccess) - { - glGetProgramInfoLog(glContext.programID, 512, 0, programInfoLog); + GLsizei maxTransforms = MAX_TRANSFORMS; + GLsizei totalSize = maxTransforms * sizeof(Transform); - SM_ASSERT(0, "Failed to link program: %s", programInfoLog); - return false; - } - } + glGenBuffers(1, (GLuint*)&glContext.vboID); + glBindBuffer(GL_ARRAY_BUFFER, glContext.vboID); + glBufferData(GL_ARRAY_BUFFER, totalSize, nullptr, GL_DYNAMIC_DRAW); - // This is preemtively, because they are still bound - // They are already marked for deletion tho - glDetachShader(glContext.programID, vertShaderID); - glDetachShader(glContext.programID, fragShaderID); - glDeleteShader(vertShaderID); - glDeleteShader(fragShaderID); + SM_ASSERT(glGetError() == GL_NO_ERROR, + "Failed to allocate memory for Vertex Buffer: %s", + get_gl_error_string(glGetError())); +} +#endif + +void create_gl_buffers() +{ +#ifdef USE_OPENGL410 + create_vertex_array_buffers(); + GLsizei stride = sizeof(Transform); + + // inPosition (location = 0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, stride, (const void*)offsetof(Transform, pos)); + glEnableVertexAttribArray(0); + glVertexAttribDivisor(0, 1); + + // inSize (location = 1) + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, stride, (const void*)offsetof(Transform, size)); + glEnableVertexAttribArray(1); + glVertexAttribDivisor(1, 1); + + // inAtlasOffset (location = 2) + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, stride, (const void*)offsetof(Transform, atlasOffset)); + glEnableVertexAttribArray(2); + glVertexAttribDivisor(2, 1); + + // inSpriteSize (location = 3) + glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, stride, (const void*)offsetof(Transform, spriteSize)); + glEnableVertexAttribArray(3); + glVertexAttribDivisor(3, 1); + + // inRenderOptions (location = 4) + glVertexAttribIPointer(4, 1, GL_INT, stride, (const void*)offsetof(Transform, renderOptions)); + glEnableVertexAttribArray(4); + glVertexAttribDivisor(4, 1); + + // inLayer (location = 5) + glVertexAttribPointer(5, 1, GL_FLOAT, GL_FALSE, stride, (const void*)offsetof(Transform, layer)); + glEnableVertexAttribArray(5); + glVertexAttribDivisor(5, 1); + + // Unbind + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + +#else // This needs to be bound, otherwise OpenGL doesn't draw anything // We won't use it tho! int VAO = 0; @@ -235,26 +319,64 @@ bool gl_init(BumpAllocator* transientStorage) { glGenBuffers(1, (GLuint*)&glContext.transformSBOID); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, glContext.transformSBOID); - glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(Transform) * MAX_TRANSFORMS, - renderData->transforms.elements, GL_DYNAMIC_DRAW); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(Transform) * MAX_TRANSFORMS, renderData->transforms.elements, GL_DYNAMIC_DRAW); } +#endif // Uniforms { glContext.screenSizeID = glGetUniformLocation(glContext.programID, "screenSize"); glContext.projectionID = glGetUniformLocation(glContext.programID, "orthoProjection"); } +} + +bool gl_init(BumpAllocator* transientStorage) +{ + load_gl_functions(); + + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + glEnable(GL_DEBUG_OUTPUT); + +#ifdef USE_OPENGL410 + char* vertShaderName = "assets/shaders/quad_41.vert"; + char* fragShaderName = "assets/shaders/quad_41.frag"; +#else + char* vertShaderName = "assets/shaders/quad.vert"; + char* fragShaderName = "assets/shaders/quad.frag"; +#endif + + GLuint vertShaderID = gl_create_shader(GL_VERTEX_SHADER, vertShaderName, transientStorage); + GLuint fragShaderID = gl_create_shader(GL_FRAGMENT_SHADER, fragShaderName, transientStorage); + if (!vertShaderID || !fragShaderID) + { + SM_ASSERT(false, "Failed to create Shaders") + return false; + } + + glContext.programID = glCreateProgram(); + glAttachShader(glContext.programID, vertShaderID); + glAttachShader(glContext.programID, fragShaderID); + glLinkProgram(glContext.programID); + check_link_errors(glContext.programID); + + // This is preemtively, because they are still bound + // They are already marked for deletion tho + glDetachShader(glContext.programID, vertShaderID); + glDetachShader(glContext.programID, fragShaderID); + glDeleteShader(vertShaderID); + glDeleteShader(fragShaderID); + + create_gl_buffers(); // Load our first Texture { int width, height, nChannels; - char* data = (char*)stbi_load(TEXTURE_PATH, - &width, &height, &nChannels, 4); + char* data = (char*)stbi_load(TEXTURE_PATH, &width, &height, &nChannels, 4); int textureSizeInBytes = 4 * width * height; glContext.textureTimestamp = get_timestamp(TEXTURE_PATH); - if(!data) + if (!data) { SM_ASSERT(0, "Failed to load Texture!"); return false; @@ -276,8 +398,7 @@ bool gl_init(BumpAllocator* transientStorage) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, width, height, - 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); stbi_image_free(data); } @@ -289,9 +410,10 @@ bool gl_init(BumpAllocator* transientStorage) glUseProgram(glContext.programID); // sRGB output (even if input texture is non-sRGB -> don't rely on texture used) - // Your font is not using sRGB, for example (not that it matters there, because no actual color is sampled from it) - // But this could prevent some future bug when you start mixing different types of textures - // Of course, you still need to correctly set the image file source format when using glTexImage2D() + // Your font is not using sRGB, for example (not that it matters there, because no + // actual color is sampled from it) But this could prevent some future bug when you + // start mixing different types of textures Of course, you still need to correctly set + // the image file source format when using glTexImage2D() glEnable(GL_FRAMEBUFFER_SRGB); glDisable(0x809D); // disable multisampling @@ -301,18 +423,42 @@ bool gl_init(BumpAllocator* transientStorage) return true; } +// ############################################################################# +// Render Interface Implementations +// ############################################################################# +template +void drawObjects(GLuint buffer, GLenum target, Array& transformData, bool isTransparent = false) +{ + if (isTransparent) + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + glBindBuffer(target, buffer); + glBufferSubData(target, 0, transformData.count * sizeof(T), transformData.elements); + + glDrawArraysInstanced(GL_TRIANGLES, 0, 6, transformData.count); + transformData.clear(); + + if (isTransparent) + { + glDisable(GL_BLEND); + } +} + void gl_render() { // Texture Hot Reloading { long long currentTimestamp = get_timestamp(TEXTURE_PATH); - if(currentTimestamp > glContext.textureTimestamp) - { + if (currentTimestamp > glContext.textureTimestamp) + { glActiveTexture(GL_TEXTURE0); int width, height, nChannels; char* data = (char*)stbi_load(TEXTURE_PATH, &width, &height, &nChannels, 4); - if(data) + if (data) { glContext.textureTimestamp = currentTimestamp; glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); @@ -334,103 +480,45 @@ void gl_render() // Copy screenSize to the GPU glUniform2fv(glContext.screenSizeID, 1, &input->screenSize.x); - // Game Pass - { - // Calculate projection matrix for 2D Game - { - glUniformMatrix4fv(glContext.projectionID, 1, GL_FALSE, - &renderData->orthoProjectionGame.ax); - } - - // Opaque Objects - { - // Copy transforms to the GPU - glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, sizeof(Transform) * MAX_TRANSFORMS, - renderData->transforms.elements); - // Reset for next Frame - - glDrawArraysInstanced(GL_TRIANGLES, 0, 6, renderData->transforms.count); - renderData->transforms.clear(); - } - - // Transparent Objects - { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - // Copy transparent transforms to the GPU - glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, sizeof(Transform) * MAX_TRANSFORMS, - renderData->transparentTransforms.elements); - // Reset for next Frame - glDrawArraysInstanced(GL_TRIANGLES, 0, 6, renderData->transparentTransforms.count); - glDisable(GL_BLEND); - renderData->transparentTransforms.clear(); - } - } - - // UI Pass - { - // Calculate projection matrix for 2D UI - { - glUniformMatrix4fv(glContext.projectionID, 1, GL_FALSE, - &renderData->orthoProjectionUI.ax); - } - - // Opaque Objects - { - // Copy transforms to the GPU - glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, sizeof(Transform) * MAX_TRANSFORMS, - renderData->uiTransforms.elements); - // Reset for next Frame - - glDrawArraysInstanced(GL_TRIANGLES, 0, 6, renderData->uiTransforms.count); - renderData->uiTransforms.clear(); - } - - // Transparent Objects - { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - // Copy transparent transforms to the GPU - glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, sizeof(Transform) * MAX_TRANSFORMS, - renderData->uiTransparentTransforms.elements); - // Reset for next Frame - glDrawArraysInstanced(GL_TRIANGLES, 0, 6, renderData->uiTransparentTransforms.count); - glDisable(GL_BLEND); - renderData->uiTransparentTransforms.clear(); - } - } +#ifdef USE_OPENGL410 + glBindVertexArray(glContext.vaoID); + + ///////////////////////////////////////////////// + // Draw Game + glUniformMatrix4fv(glContext.projectionID, 1, GL_FALSE, &renderData->orthoProjectionGame.ax); + // Opaque Game Objects + drawObjects(glContext.vboID, GL_ARRAY_BUFFER, renderData->transforms); + // Transparent Game Objects + drawObjects(glContext.vboID, GL_ARRAY_BUFFER, renderData->transparentTransforms, true); + + ///////////////////////////////////////////////// + // Draw UI + glUniformMatrix4fv(glContext.projectionID, 1, GL_FALSE, &renderData->orthoProjectionUI.ax); + // Opaque UI objects + drawObjects(glContext.vboID, GL_ARRAY_BUFFER, renderData->uiTransforms); + // Transparent UI objects + drawObjects(glContext.vboID, GL_ARRAY_BUFFER, renderData->uiTransparentTransforms, true); + + glBindBuffer(GL_ARRAY_BUFFER, 0); // Unbind the VBO + glBindVertexArray(0); // Unbind the VAO +#else + glBindBuffer(GL_SHADER_STORAGE_BUFFER, glContext.transformSBOID); + + ///////////////////////////////////////////////// + // Draw Game + glUniformMatrix4fv(glContext.projectionID, 1, GL_FALSE, &renderData->orthoProjectionGame.ax); + // Opaque Game Objects + drawObjects(glContext.transformSBOID, GL_SHADER_STORAGE_BUFFER, renderData->transforms); + // Transparent Game Objects + drawObjects(glContext.transformSBOID, GL_SHADER_STORAGE_BUFFER, renderData->transparentTransforms, true); + + ///////////////////////////////////////////////// + // Draw UI + glUniformMatrix4fv(glContext.projectionID, 1, GL_FALSE, &renderData->orthoProjectionUI.ax); + // Opaque UI objects + drawObjects(glContext.transformSBOID, GL_SHADER_STORAGE_BUFFER, renderData->uiTransforms); + // Transparent UI objects + drawObjects(glContext.transformSBOID, GL_SHADER_STORAGE_BUFFER, renderData->uiTransparentTransforms, true); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); // Unbind the SSBO +#endif } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/gl_renderer.h b/src/gl_renderer.h index 7bce185..f1d913a 100644 --- a/src/gl_renderer.h +++ b/src/gl_renderer.h @@ -57,7 +57,7 @@ static PFNGLDETACHSHADERPROC glDetachShader_ptr; static PFNGLDELETESHADERPROC glDeleteShader_ptr; static PFNGLDRAWELEMENTSINSTANCEDPROC glDrawElementsInstanced_ptr; static PFNGLGENERATEMIPMAPPROC glGenerateMipmap_ptr; - +static PFNGLVERTEXATTRIBIPOINTERPROC glVertexAttribIPointer_ptr; void load_gl_functions() { @@ -114,6 +114,7 @@ void load_gl_functions() glDeleteShader_ptr = (PFNGLDELETESHADERPROC) platform_load_gl_func("glDeleteShader"); glDrawElementsInstanced_ptr = (PFNGLDRAWELEMENTSINSTANCEDPROC) platform_load_gl_func("glDrawElementsInstanced"); glGenerateMipmap_ptr = (PFNGLGENERATEMIPMAPPROC) platform_load_gl_func("glGenerateMipmap"); + glVertexAttribIPointer_ptr = (PFNGLVERTEXATTRIBIPOINTERPROC)platform_load_gl_func("glVertexAttribIPointer"); } // ############################################################################# @@ -379,6 +380,11 @@ void glGenerateMipmap(GLenum target) glGenerateMipmap_ptr(target); } +GLAPI void APIENTRY glVertexAttribIPointer (GLuint index, GLint size, GLenum type, GLsizei stride, const void* pointer) +{ + glVertexAttribIPointer_ptr(index, size, type, stride, pointer); +} + // Loaded by default it seems, but I kept them here, just in case, must be OpenGL 1.0, and static linking /* static PFNGLTEXIMAGE2DPROC glTexImage2D_ptr; diff --git a/src/input.h b/src/input.h index 6f37bf8..4cc2fff 100644 --- a/src/input.h +++ b/src/input.h @@ -4,7 +4,7 @@ // ############################################################################# // Input Constants // ############################################################################# -constexpr int MAX_KEYCODES = 256; +constexpr int MAX_KEYCODES = 384; // ############################################################################# // Input Structs @@ -56,7 +56,7 @@ enum KeyCodeID KEY_CONTROL, KEY_ALT, KEY_COMMAND, - + KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, diff --git a/src/mac_platform.cpp b/src/mac_platform.cpp index cc050dc..4b9198d 100644 --- a/src/mac_platform.cpp +++ b/src/mac_platform.cpp @@ -1,16 +1,332 @@ // mac_platform.cpp -// Bridge to Objective-C code -extern bool running; // <- declare, but do not define +#include "input.h" +#include "platform.h" +#include "schnitzel_lib.h" -extern "C" { - bool platform_create_window_objc(int width, int height, char* title); - void platform_update_window_objc(); +// Suppress deprecation warnings for OpenGL functions on Mac +#define GL_SILENCE_DEPRECATION + +// Prevent GLFW from including any OpenGL headers on its own, +// to avoid conflicts with the included OpenGL headers. +#define GLFW_INCLUDE_NONE + +#include // Includes the GLFW library for creating windows and handling input. +#include // Includes the OpenGL 3 header for rendering. + +#include // for loading the so (DLL) file +#include // For dirname function +#include // for sleep + +GLFWwindow* window; +const char* window_name = "Schnitzel Motor"; + +// ############################################################################# +// Mac Globals +// ############################################################################# +extern bool running; + +void error_callback(int error, const char* description) +{ + SM_ERROR("GLFW Error %d: %s", error, description); +} + +void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) +{ + // Coomand-Q to quit the application + if (action == GLFW_PRESS && GLFW_MOD_CONTROL && key == GLFW_KEY_Q) + { + glfwSetWindowShouldClose(window, GLFW_TRUE); + running = false; + } + + // Ensure the key is within our lookup table range + if (key >= 0 && key < sizeof(KeyCodeLookupTable) / sizeof(KeyCodeLookupTable[0])) + { + bool isDown = action == GLFW_PRESS || action == GLFW_REPEAT; + KeyCodeID keyCode = KeyCodeLookupTable[key]; + Key* keyState = &input->keys[keyCode]; + + // Update the key state + if (isDown) + { + // Key is down + keyState->justPressed = !keyState->isDown; + keyState->halfTransitionCount++; + } + else if (action == GLFW_RELEASE) + { + // Key is up + keyState->justReleased = keyState->isDown; + keyState->halfTransitionCount++; + } + + keyState->isDown = isDown; + } +} + +void cursor_position_callback(GLFWwindow* window, double xpos, double ypos) +{ + int scaledWidth, scaledHeight; + + glfwGetWindowSize(window, &scaledWidth, &scaledHeight); + // scale up the mouse position so that it matches the screen size + float scaleX = (float)input->screenSize.x * 1.0f / (float)scaledWidth; + float scaleY = (float)input->screenSize.y * 1.0f / (float)scaledHeight; + + input->prevMousePos = input->mousePos; + input->mousePos = {(int)(xpos * scaleX), (int)(ypos * scaleY)}; + input->relMouse = input->mousePos - input->prevMousePos; +} + +void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods) +{ + // Get mouse position + double win_x, win_y; + glfwGetCursorPos(window, &win_x, &win_y); + + int scaledWidth, scaledHeight; + glfwGetWindowSize(window, &scaledWidth, &scaledHeight); + // scale up the mouse position so that it matches the screen size + float scaleX = (float)input->screenSize.x * 1.0f / (float)scaledWidth; + float scaleY = (float)input->screenSize.y * 1.0f / (float)scaledHeight; + + input->prevMousePos = input->mousePos; + input->mousePos = {(int)(win_x * scaleX), (int)(win_y * scaleY)}; + input->relMouse = input->mousePos - input->prevMousePos; + + // Map GLFW mouse button to your KeyCode + KeyCodeID keyCode = (button == GLFW_MOUSE_BUTTON_LEFT) ? KEY_MOUSE_LEFT : KEY_MOUSE_RIGHT; + Key* keyState = &input->keys[keyCode]; + + // Determine the button state + bool isDown = (action == GLFW_PRESS); + + // Update the key state + keyState->justPressed = !keyState->justPressed && !keyState->isDown && isDown; + keyState->justReleased = !keyState->justReleased && keyState->isDown && !isDown; + keyState->isDown = isDown; + keyState->halfTransitionCount++; +} + +void frameBufferResizeCallback(GLFWwindow* window, int width, int height) +{ + glViewport(0, 0, width, height); + input->screenSize.x = width; + input->screenSize.y = height; +} + +void window_close_callback(GLFWwindow* window) +{ + running = false; +} + +void platform_fill_keycode_lookup_table() +{ + KeyCodeLookupTable[GLFW_KEY_A] = KEY_A; + KeyCodeLookupTable[GLFW_KEY_B] = KEY_B; + KeyCodeLookupTable[GLFW_KEY_C] = KEY_C; + KeyCodeLookupTable[GLFW_KEY_D] = KEY_D; + KeyCodeLookupTable[GLFW_KEY_E] = KEY_E; + KeyCodeLookupTable[GLFW_KEY_F] = KEY_F; + KeyCodeLookupTable[GLFW_KEY_G] = KEY_G; + KeyCodeLookupTable[GLFW_KEY_H] = KEY_H; + KeyCodeLookupTable[GLFW_KEY_I] = KEY_I; + KeyCodeLookupTable[GLFW_KEY_J] = KEY_J; + KeyCodeLookupTable[GLFW_KEY_K] = KEY_K; + KeyCodeLookupTable[GLFW_KEY_L] = KEY_L; + KeyCodeLookupTable[GLFW_KEY_M] = KEY_M; + KeyCodeLookupTable[GLFW_KEY_N] = KEY_N; + KeyCodeLookupTable[GLFW_KEY_O] = KEY_O; + KeyCodeLookupTable[GLFW_KEY_P] = KEY_P; + KeyCodeLookupTable[GLFW_KEY_Q] = KEY_Q; + KeyCodeLookupTable[GLFW_KEY_R] = KEY_R; + KeyCodeLookupTable[GLFW_KEY_S] = KEY_S; + KeyCodeLookupTable[GLFW_KEY_T] = KEY_T; + KeyCodeLookupTable[GLFW_KEY_U] = KEY_U; + KeyCodeLookupTable[GLFW_KEY_V] = KEY_V; + KeyCodeLookupTable[GLFW_KEY_W] = KEY_W; + KeyCodeLookupTable[GLFW_KEY_X] = KEY_X; + KeyCodeLookupTable[GLFW_KEY_Y] = KEY_Y; + KeyCodeLookupTable[GLFW_KEY_Z] = KEY_Z; + KeyCodeLookupTable[GLFW_KEY_0] = KEY_0; + KeyCodeLookupTable[GLFW_KEY_1] = KEY_1; + KeyCodeLookupTable[GLFW_KEY_2] = KEY_2; + KeyCodeLookupTable[GLFW_KEY_3] = KEY_3; + KeyCodeLookupTable[GLFW_KEY_4] = KEY_4; + KeyCodeLookupTable[GLFW_KEY_5] = KEY_5; + KeyCodeLookupTable[GLFW_KEY_6] = KEY_6; + KeyCodeLookupTable[GLFW_KEY_7] = KEY_7; + KeyCodeLookupTable[GLFW_KEY_8] = KEY_8; + KeyCodeLookupTable[GLFW_KEY_9] = KEY_9; + KeyCodeLookupTable[GLFW_KEY_SPACE] = KEY_SPACE; + KeyCodeLookupTable[GLFW_KEY_APOSTROPHE] = KEY_TICK; + KeyCodeLookupTable[GLFW_KEY_MINUS] = KEY_MINUS; + KeyCodeLookupTable[GLFW_KEY_EQUAL] = KEY_EQUAL; + KeyCodeLookupTable[GLFW_KEY_LEFT_BRACKET] = KEY_LEFT_BRACKET; + KeyCodeLookupTable[GLFW_KEY_RIGHT_BRACKET] = KEY_RIGHT_BRACKET; + KeyCodeLookupTable[GLFW_KEY_SEMICOLON] = KEY_SEMICOLON; + KeyCodeLookupTable[GLFW_KEY_APOSTROPHE] = KEY_QUOTE; + KeyCodeLookupTable[GLFW_KEY_COMMA] = KEY_COMMA; + KeyCodeLookupTable[GLFW_KEY_PERIOD] = KEY_PERIOD; + KeyCodeLookupTable[GLFW_KEY_SLASH] = KEY_FORWARD_SLASH; + KeyCodeLookupTable[GLFW_KEY_BACKSLASH] = KEY_BACKWARD_SLASH; + KeyCodeLookupTable[GLFW_KEY_TAB] = KEY_TAB; + KeyCodeLookupTable[GLFW_KEY_ESCAPE] = KEY_ESCAPE; + KeyCodeLookupTable[GLFW_KEY_PAUSE] = KEY_PAUSE; + KeyCodeLookupTable[GLFW_KEY_UP] = KEY_UP; + KeyCodeLookupTable[GLFW_KEY_DOWN] = KEY_DOWN; + KeyCodeLookupTable[GLFW_KEY_LEFT] = KEY_LEFT; + KeyCodeLookupTable[GLFW_KEY_RIGHT] = KEY_RIGHT; + KeyCodeLookupTable[GLFW_KEY_BACKSPACE] = KEY_BACKSPACE; + KeyCodeLookupTable[GLFW_KEY_ENTER] = KEY_RETURN; + KeyCodeLookupTable[GLFW_KEY_DELETE] = KEY_DELETE; + KeyCodeLookupTable[GLFW_KEY_INSERT] = KEY_INSERT; + KeyCodeLookupTable[GLFW_KEY_HOME] = KEY_HOME; + KeyCodeLookupTable[GLFW_KEY_END] = KEY_END; + KeyCodeLookupTable[GLFW_KEY_PAGE_UP] = KEY_PAGE_UP; + KeyCodeLookupTable[GLFW_KEY_PAGE_DOWN] = KEY_PAGE_DOWN; + KeyCodeLookupTable[GLFW_KEY_CAPS_LOCK] = KEY_CAPS_LOCK; + KeyCodeLookupTable[GLFW_KEY_NUM_LOCK] = KEY_NUM_LOCK; + KeyCodeLookupTable[GLFW_KEY_SCROLL_LOCK] = KEY_SCROLL_LOCK; + KeyCodeLookupTable[GLFW_KEY_LEFT_SHIFT] = KEY_SHIFT; + KeyCodeLookupTable[GLFW_KEY_RIGHT_SHIFT] = KEY_SHIFT; + KeyCodeLookupTable[GLFW_KEY_LEFT_CONTROL] = KEY_CONTROL; + KeyCodeLookupTable[GLFW_KEY_RIGHT_CONTROL] = KEY_CONTROL; + KeyCodeLookupTable[GLFW_KEY_LEFT_ALT] = KEY_ALT; + KeyCodeLookupTable[GLFW_KEY_RIGHT_ALT] = KEY_ALT; + KeyCodeLookupTable[GLFW_KEY_F1] = KEY_F1; + KeyCodeLookupTable[GLFW_KEY_F2] = KEY_F2; + KeyCodeLookupTable[GLFW_KEY_F3] = KEY_F3; + KeyCodeLookupTable[GLFW_KEY_F4] = KEY_F4; + KeyCodeLookupTable[GLFW_KEY_F5] = KEY_F5; + KeyCodeLookupTable[GLFW_KEY_F6] = KEY_F6; + KeyCodeLookupTable[GLFW_KEY_F7] = KEY_F7; + KeyCodeLookupTable[GLFW_KEY_F8] = KEY_F8; + KeyCodeLookupTable[GLFW_KEY_F9] = KEY_F9; + KeyCodeLookupTable[GLFW_KEY_F10] = KEY_F10; + KeyCodeLookupTable[GLFW_KEY_F11] = KEY_F11; + KeyCodeLookupTable[GLFW_KEY_F12] = KEY_F12; + KeyCodeLookupTable[GLFW_KEY_KP_0] = KEY_NUMPAD_0; + KeyCodeLookupTable[GLFW_KEY_KP_1] = KEY_NUMPAD_1; + KeyCodeLookupTable[GLFW_KEY_KP_2] = KEY_NUMPAD_2; + KeyCodeLookupTable[GLFW_KEY_KP_3] = KEY_NUMPAD_3; + KeyCodeLookupTable[GLFW_KEY_KP_4] = KEY_NUMPAD_4; + KeyCodeLookupTable[GLFW_KEY_KP_5] = KEY_NUMPAD_5; + KeyCodeLookupTable[GLFW_KEY_KP_6] = KEY_NUMPAD_6; + KeyCodeLookupTable[GLFW_KEY_KP_7] = KEY_NUMPAD_7; + KeyCodeLookupTable[GLFW_KEY_KP_8] = KEY_NUMPAD_8; + KeyCodeLookupTable[GLFW_KEY_KP_9] = KEY_NUMPAD_9; +} + +bool platform_create_window(int width, int height, char* title) +{ + glfwSetErrorCallback(error_callback); + + if (!glfwInit()) + return false; + + glfwWindowHint(GLFW_SAMPLES, 0); // Disable anti-aliasing + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + + input->screenSize.x = width; + input->screenSize.y = height; + + window = glfwCreateWindow(width, height, window_name, nullptr, nullptr); + + if (!window) + { + glfwTerminate(); + return false; + } + + // Get the actual window size + int screenWidth, screenHeight; + glfwGetFramebufferSize(window, &screenWidth, &screenHeight); + glfwSetKeyCallback(window, keyCallback); + glfwSetFramebufferSizeCallback(window, frameBufferResizeCallback); + glfwMakeContextCurrent(window); + glfwSetMouseButtonCallback(window, mouseButtonCallback); + glfwSetWindowCloseCallback(window, window_close_callback); + // Set the cursor position callback. + glfwSetCursorPosCallback(window, cursor_position_callback); + + glfwSwapInterval(1); + glClearColor(0.2f, 0.02f, 0.2f, 1.0f); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_CULL_FACE); + glFrontFace(GL_CCW); + glDisable(GL_DEPTH_TEST); + glDisable(GL_SCISSOR_TEST); + + glfwSetCursor(window, glfwCreateStandardCursor(GLFW_ARROW_CURSOR)); + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + + input->screenSize.x = screenWidth; + input->screenSize.y = screenHeight; + return true; +} + +void platform_update_window() +{ + glfwPollEvents(); +} + +void* platform_load_gl_func(char* funName) +{ + return (void*)glfwGetProcAddress(funName); +} + +void platform_swap_buffers() +{ + glfwSwapBuffers(window); +} + +void platform_set_vsync(bool vSync) +{ + glfwSwapInterval(vSync ? 1 : 0); +} + +void* platform_load_dynamic_library(char* dll) +{ + void* handle = dlopen(dll, RTLD_NOW); + SM_ASSERT(handle, "Failed to load dynamic library: %s", dll); + + return handle; +} + +void* platform_load_dynamic_function(void* dll, const char* funName) +{ + void* proc = dlsym(dll, funName); + SM_ASSERT(proc, "Failed to load function: %s from dynamic library", funName); + + return proc; +} + +bool platform_free_dynamic_library(void* dll) +{ + SM_ASSERT(dll, "No dynamic library supplied!"); + int freeResult = dlclose(dll); + SM_ASSERT(freeResult == 0, "Failed to dlclose"); + + return (freeResult == 0); +} + +bool platform_init_audio() +{ + return true; } -inline bool platform_create_window(int width, int height, char* title) { - return platform_create_window_objc(width, height, title); +void platform_update_audio(float dt) +{ + soundState->playingSounds.clear(); } -inline void platform_update_window() { - platform_update_window_objc(); +void platform_sleep(unsigned int ms) +{ + sleep(ms); } \ No newline at end of file diff --git a/src/mac_platform.m b/src/mac_platform.m deleted file mode 100644 index 66f029e..0000000 --- a/src/mac_platform.m +++ /dev/null @@ -1,56 +0,0 @@ -#include - -static NSWindow* window; - -extern bool running; // <- declare, but do not define - -bool platform_create_window_objc(int width, int height, char* title) -{ - // Start the shared application - [NSApplication sharedApplication]; - - // Set the activation policy to regular before creating the window - [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; - - // Create the main window - CGFloat screenWidth = [[NSScreen mainScreen] frame].size.width; - CGFloat screenHeight = [[NSScreen mainScreen] frame].size.height; - CGFloat windowX = (screenWidth - width) / 2; - CGFloat windowY = (screenHeight - height) / 2; - NSRect frame = NSMakeRect(windowX, windowY, width, height); - - NSUInteger styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable; - window = [[NSWindow alloc] initWithContentRect:frame styleMask:styleMask backing:NSBackingStoreBuffered defer:NO]; - [window setTitle:[NSString stringWithUTF8String:title]]; - [window setLevel:NSStatusWindowLevel]; // Set window level top-most (above all other windows) - [window makeKeyAndOrderFront:nil]; - [window orderFrontRegardless]; // Bring the main window into focus - - // Activate the window to give it focus - [NSApp activateIgnoringOtherApps:YES]; - - return true; -} - -void platform_update_window_objc() -{ - NSEvent* event; - while ((event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate date] inMode:NSDefaultRunLoopMode dequeue:YES]) && running) { - // Handle other events like window resizing, closing, etc. - [NSApp sendEvent:event]; - - // Check for keyboard events - if ([event type] == NSEventTypeKeyDown) { - NSString *characters = [event charactersIgnoringModifiers]; - NSEventModifierFlags flags = [event modifierFlags]; - - // Handle window close shortcut 'cmd + q' - if (flags & NSEventModifierFlagCommand && [characters isEqualToString:@"q"]) { - running = false; // Exit the application - break; // Stop processing events - } - } - } - - [NSApp updateWindows]; -} diff --git a/src/main.cpp b/src/main.cpp index 9b8d32c..736bdfc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,9 +21,10 @@ const char* gameLibName = "game.dll"; const char* gameLoadLibName = "game_load.dll"; #elif defined(__APPLE__) +#define USE_OPENGL410 // Use Legacy OpenGL 4.1 on Mac #include "mac_platform.cpp" -const char* gameLibName = "game.so"; // ????? -const char* gameLoadLibName = "game_load.so"; +char* gameLibName = "game.dylib"; +char* gameLoadLibName = "game_load.dylib"; #else // Linux #include "linux_platform.cpp" const char* gameLibName = "game.so"; diff --git a/src/schnitzel_lib.h b/src/schnitzel_lib.h index c04f982..010e777 100644 --- a/src/schnitzel_lib.h +++ b/src/schnitzel_lib.h @@ -103,8 +103,8 @@ void _log(char* prefix, char* msg, TextColor textColor, Args... args) } #define SM_TRACE(msg, ...) _log("TRACE:", msg, TEXT_COLOR_GREEN, ##__VA_ARGS__); -#define SM_WARN(msg, ...) _log("WARN:", msg, TEXT_COLOR_YELLOW, "\033[0m", ##__VA_ARGS__); -#define SM_ERROR(msg, ...) _log("ERROR:", msg, TEXT_COLOR_RED, "\033[0m", ##__VA_ARGS__); +#define SM_WARN(msg, ...) _log("WARN:", msg, TEXT_COLOR_YELLOW , ##__VA_ARGS__); +#define SM_ERROR(msg, ...) _log("ERROR:", msg, TEXT_COLOR_RED, ##__VA_ARGS__); #define SM_ASSERT(x, msg, ...) \ { \ @@ -134,7 +134,7 @@ struct Array int add(T element) { - SM_ASSERT(count < maxElements, "Array Full!"); + SM_ASSERT(count < maxElements, "Array Full!"); elements[count] = element; return count++; } diff --git a/src/ui.h b/src/ui.h index e24abe0..8c7f038 100644 --- a/src/ui.h +++ b/src/ui.h @@ -3,7 +3,6 @@ #include "input.h" #include "render_interface.h" - // ############################################################################# // UI Constants // ############################################################################# @@ -52,8 +51,7 @@ static UIState* uiState; // ############################################################################# void update_ui() { - if(!key_is_down(KEY_MOUSE_LEFT) && - !key_released_this_frame(KEY_MOUSE_LEFT)) + if (!key_is_down(KEY_MOUSE_LEFT) && !key_released_this_frame(KEY_MOUSE_LEFT)) { uiState->active = {}; } @@ -70,7 +68,7 @@ void set_active(int ID) void set_hot(int ID, int layer = 0) { - if(uiState->hotThisFrame.layer <= layer) + if (uiState->hotThisFrame.layer <= layer) { uiState->hotThisFrame.ID = ID; uiState->hotThisFrame.layer = layer; @@ -97,36 +95,32 @@ bool do_button(SpriteID spriteID, IVec2 pos, int ID) IVec2 mousePosWold = screen_to_ui(input->mousePos); // Draw UI Element (Adds to an array of elements to draw during render()) { - UIElement uiElement = - { - .spriteID = spriteID, - .pos = vec_2(pos), + UIElement uiElement = { + .spriteID = spriteID, + .pos = vec_2(pos), }; uiState->uiElements.add(uiElement); } - IRect rect = {pos.x - sprite.size.x / 2, - pos.y - sprite.size.y / 2, - sprite.size}; - if(is_active(ID)) + IRect rect = {pos.x - sprite.size.x / 2, pos.y - sprite.size.y / 2, sprite.size}; + if (is_active(ID)) { - if(key_released_this_frame(KEY_MOUSE_LEFT) && - point_in_rect(mousePosWold, rect)) + if (key_released_this_frame(KEY_MOUSE_LEFT) && point_in_rect(mousePosWold, rect)) { - // Set inactive + // Set inactive uiState->active = {}; return true; } } - else if(is_hot(ID)) + else if (is_hot(ID)) { - if(key_pressed_this_frame(KEY_MOUSE_LEFT)) + if (key_pressed_this_frame(KEY_MOUSE_LEFT)) { set_active(ID); } } - if(point_in_rect(mousePosWold, rect)) + if (point_in_rect(mousePosWold, rect)) { set_hot(ID); } @@ -144,10 +138,8 @@ void do_ui_text(char* text, Vec2 pos) uiState->texts.add(uiText); } -template -void do_format_ui_text(char* format, Vec2 pos, Args... args) +template void do_format_ui_text(char* format, Vec2 pos, Args... args) { char* text = format_text(format, args...); do_ui_text(text, pos); } -