From fca5628279ce87e328e753a9d66c4fa65fe9124b Mon Sep 17 00:00:00 2001 From: Ronja Enseleit Date: Sat, 1 Jul 2023 20:38:43 +0200 Subject: [PATCH] Restructuring for platform independent rendering --- app/src/main/KDE.xcodeproj/project.pbxproj | 58 +++- .../UserInterfaceState.xcuserstate | Bin 44411 -> 113048 bytes .../xcdebugger/Breakpoints_v2.xcbkptlist | 24 ++ app/src/main/cpp/AssetManager.h | 2 +- app/src/main/cpp/Gfx.h | 15 + app/src/main/cpp/OpenGLGFXInterface.cpp | 285 ++++++++++++++++++ app/src/main/cpp/OpenGLGFXInterface.h | 25 ++ app/src/main/cpp/Renderer.cpp | 282 +++-------------- app/src/main/cpp/Renderer.h | 56 +--- app/src/main/cpp/StringRepository.cpp | 12 +- app/src/main/cpp/StringRepository.h | 16 +- ...ViewController.m => GameViewController.mm} | 22 +- app/src/main/m/MetalGfxInterface.h | 11 + app/src/main/m/MetalGfxInterface.mm | 12 + .../m/{Renderer.h => MetalViewDelegate.h} | 9 +- .../m/{Renderer.m => MetalViewDelegate.mm} | 38 ++- 16 files changed, 552 insertions(+), 315 deletions(-) create mode 100644 app/src/main/KDE.xcodeproj/xcuserdata/ronjaenseleit.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist create mode 100644 app/src/main/cpp/Gfx.h create mode 100644 app/src/main/cpp/OpenGLGFXInterface.cpp create mode 100644 app/src/main/cpp/OpenGLGFXInterface.h rename app/src/main/m/{GameViewController.m => GameViewController.mm} (72%) create mode 100644 app/src/main/m/MetalGfxInterface.h create mode 100644 app/src/main/m/MetalGfxInterface.mm rename app/src/main/m/{Renderer.h => MetalViewDelegate.h} (52%) rename app/src/main/m/{Renderer.m => MetalViewDelegate.mm} (93%) diff --git a/app/src/main/KDE.xcodeproj/project.pbxproj b/app/src/main/KDE.xcodeproj/project.pbxproj index 1896a4f..3ec4e07 100644 --- a/app/src/main/KDE.xcodeproj/project.pbxproj +++ b/app/src/main/KDE.xcodeproj/project.pbxproj @@ -8,8 +8,8 @@ /* Begin PBXBuildFile section */ FBA9A7502A50790A00F960DA /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = FBA9A74F2A50790A00F960DA /* AppDelegate.m */; }; - FBA9A7532A50790A00F960DA /* Renderer.m in Sources */ = {isa = PBXBuildFile; fileRef = FBA9A7522A50790A00F960DA /* Renderer.m */; }; - FBA9A7562A50790A00F960DA /* GameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FBA9A7552A50790A00F960DA /* GameViewController.m */; }; + FBA9A7532A50790A00F960DA /* MetalViewDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = FBA9A7522A50790A00F960DA /* MetalViewDelegate.mm */; }; + FBA9A7562A50790A00F960DA /* GameViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = FBA9A7552A50790A00F960DA /* GameViewController.mm */; }; FBA9A7582A50790A00F960DA /* Shaders.metal in Sources */ = {isa = PBXBuildFile; fileRef = FBA9A7572A50790A00F960DA /* Shaders.metal */; }; FBA9A75C2A50790A00F960DA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FBA9A75A2A50790A00F960DA /* Main.storyboard */; }; FBA9A75E2A50790A00F960DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FBA9A75D2A50790A00F960DA /* Assets.xcassets */; }; @@ -19,6 +19,11 @@ FBA9A7692A50795400F960DA /* AssetManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FBA9A73D2A5078B800F960DA /* AssetManager.cpp */; }; FBA9A76A2A50795700F960DA /* Hash.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FBA9A73A2A5078B800F960DA /* Hash.cpp */; }; FBA9A76B2A5079BC00F960DA /* Texture.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FBA9A73F2A5078D800F960DA /* Texture.cpp */; }; + FBA9A7702A5098E800F960DA /* MetalGfxInterface.mm in Sources */ = {isa = PBXBuildFile; fileRef = FBA9A76F2A5098E800F960DA /* MetalGfxInterface.mm */; }; + FBA9A7762A50A58000F960DA /* Renderer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FBA9A7722A509C6500F960DA /* Renderer.cpp */; }; + FBA9A7802A50A82500F960DA /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FBA9A77E2A50A82400F960DA /* Metal.framework */; }; + FBA9A7812A50A82500F960DA /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FBA9A77F2A50A82400F960DA /* MetalKit.framework */; }; + FBA9A7832A50A82D00F960DA /* ModelIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FBA9A7822A50A82C00F960DA /* ModelIO.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -41,16 +46,26 @@ FBA9A74B2A50790A00F960DA /* KDE.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KDE.app; sourceTree = BUILT_PRODUCTS_DIR; }; FBA9A74E2A50790A00F960DA /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; FBA9A74F2A50790A00F960DA /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - FBA9A7512A50790A00F960DA /* Renderer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Renderer.h; sourceTree = ""; }; - FBA9A7522A50790A00F960DA /* Renderer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Renderer.m; sourceTree = ""; }; + FBA9A7512A50790A00F960DA /* MetalViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MetalViewDelegate.h; sourceTree = ""; }; + FBA9A7522A50790A00F960DA /* MetalViewDelegate.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MetalViewDelegate.mm; sourceTree = ""; }; FBA9A7542A50790A00F960DA /* GameViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GameViewController.h; sourceTree = ""; }; - FBA9A7552A50790A00F960DA /* GameViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GameViewController.m; sourceTree = ""; }; + FBA9A7552A50790A00F960DA /* GameViewController.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = GameViewController.mm; sourceTree = ""; }; FBA9A7572A50790A00F960DA /* Shaders.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = Shaders.metal; sourceTree = ""; }; FBA9A7592A50790A00F960DA /* ShaderTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ShaderTypes.h; sourceTree = ""; }; FBA9A75B2A50790A00F960DA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; FBA9A75D2A50790A00F960DA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; FBA9A7602A50790A00F960DA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; FBA9A7622A50790A00F960DA /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + FBA9A76C2A5096C500F960DA /* MetalGfxInterface.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MetalGfxInterface.h; sourceTree = ""; }; + FBA9A76D2A5096DF00F960DA /* Renderer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Renderer.h; path = cpp/Renderer.h; sourceTree = ""; }; + FBA9A76F2A5098E800F960DA /* MetalGfxInterface.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MetalGfxInterface.mm; sourceTree = ""; }; + FBA9A7712A509A4400F960DA /* Gfx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Gfx.h; path = cpp/Gfx.h; sourceTree = ""; }; + FBA9A7722A509C6500F960DA /* Renderer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Renderer.cpp; path = cpp/Renderer.cpp; sourceTree = ""; }; + FBA9A77A2A50A65400F960DA /* OpenGLGFXInterface.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = OpenGLGFXInterface.cpp; path = cpp/OpenGLGFXInterface.cpp; sourceTree = ""; }; + FBA9A77B2A50A65400F960DA /* OpenGLGFXInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OpenGLGFXInterface.h; path = cpp/OpenGLGFXInterface.h; sourceTree = ""; }; + FBA9A77E2A50A82400F960DA /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.4.sdk/System/Library/Frameworks/Metal.framework; sourceTree = DEVELOPER_DIR; }; + FBA9A77F2A50A82400F960DA /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.4.sdk/System/Library/Frameworks/MetalKit.framework; sourceTree = DEVELOPER_DIR; }; + FBA9A7822A50A82C00F960DA /* ModelIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ModelIO.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.4.sdk/System/Library/Frameworks/ModelIO.framework; sourceTree = DEVELOPER_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -58,6 +73,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + FBA9A7832A50A82D00F960DA /* ModelIO.framework in Frameworks */, + FBA9A7802A50A82500F960DA /* Metal.framework in Frameworks */, + FBA9A7812A50A82500F960DA /* MetalKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -67,6 +85,11 @@ FBA9A7332A50789900F960DA /* cpp */ = { isa = PBXGroup; children = ( + FBA9A77A2A50A65400F960DA /* OpenGLGFXInterface.cpp */, + FBA9A77B2A50A65400F960DA /* OpenGLGFXInterface.h */, + FBA9A7722A509C6500F960DA /* Renderer.cpp */, + FBA9A7712A509A4400F960DA /* Gfx.h */, + FBA9A76D2A5096DF00F960DA /* Renderer.h */, FBA9A7412A5078D800F960DA /* stb_image.h */, FBA9A7402A5078D800F960DA /* stb_truetype.h */, FBA9A7432A5078D800F960DA /* StringRepository.cpp */, @@ -92,16 +115,18 @@ children = ( FBA9A74E2A50790A00F960DA /* AppDelegate.h */, FBA9A74F2A50790A00F960DA /* AppDelegate.m */, - FBA9A7512A50790A00F960DA /* Renderer.h */, - FBA9A7522A50790A00F960DA /* Renderer.m */, + FBA9A7512A50790A00F960DA /* MetalViewDelegate.h */, + FBA9A7522A50790A00F960DA /* MetalViewDelegate.mm */, FBA9A7542A50790A00F960DA /* GameViewController.h */, - FBA9A7552A50790A00F960DA /* GameViewController.m */, + FBA9A7552A50790A00F960DA /* GameViewController.mm */, FBA9A7572A50790A00F960DA /* Shaders.metal */, FBA9A7592A50790A00F960DA /* ShaderTypes.h */, FBA9A75A2A50790A00F960DA /* Main.storyboard */, FBA9A75D2A50790A00F960DA /* Assets.xcassets */, FBA9A75F2A50790A00F960DA /* LaunchScreen.storyboard */, FBA9A7622A50790A00F960DA /* main.m */, + FBA9A76C2A5096C500F960DA /* MetalGfxInterface.h */, + FBA9A76F2A5098E800F960DA /* MetalGfxInterface.mm */, ); path = m; sourceTree = ""; @@ -114,12 +139,23 @@ name = Products; sourceTree = ""; }; + FBA9A77D2A50A82400F960DA /* Frameworks */ = { + isa = PBXGroup; + children = ( + FBA9A7822A50A82C00F960DA /* ModelIO.framework */, + FBA9A77E2A50A82400F960DA /* Metal.framework */, + FBA9A77F2A50A82400F960DA /* MetalKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; FBC6DF7A2A50782C00CA10E7 = { isa = PBXGroup; children = ( FBA9A7462A5078DC00F960DA /* m */, FBA9A7332A50789900F960DA /* cpp */, FBA9A74C2A50790A00F960DA /* Products */, + FBA9A77D2A50A82400F960DA /* Frameworks */, ); sourceTree = ""; }; @@ -197,10 +233,12 @@ FBA9A7632A50790A00F960DA /* main.m in Sources */, FBA9A7582A50790A00F960DA /* Shaders.metal in Sources */, FBA9A76B2A5079BC00F960DA /* Texture.cpp in Sources */, + FBA9A7762A50A58000F960DA /* Renderer.cpp in Sources */, FBA9A7682A50795000F960DA /* StringRepository.cpp in Sources */, - FBA9A7532A50790A00F960DA /* Renderer.m in Sources */, + FBA9A7702A5098E800F960DA /* MetalGfxInterface.mm in Sources */, + FBA9A7532A50790A00F960DA /* MetalViewDelegate.mm in Sources */, FBA9A76A2A50795700F960DA /* Hash.cpp in Sources */, - FBA9A7562A50790A00F960DA /* GameViewController.m in Sources */, + FBA9A7562A50790A00F960DA /* GameViewController.mm in Sources */, FBA9A7502A50790A00F960DA /* AppDelegate.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/app/src/main/KDE.xcodeproj/project.xcworkspace/xcuserdata/ronjaenseleit.xcuserdatad/UserInterfaceState.xcuserstate b/app/src/main/KDE.xcodeproj/project.xcworkspace/xcuserdata/ronjaenseleit.xcuserdatad/UserInterfaceState.xcuserstate index ca017255e1f3f4cf8258363a4e273f882c15e8ec..20b0ba40aa05c9bd7853aae63a141e01b0136471 100644 GIT binary patch literal 113048 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%0AOG#Mex=atM)P0paUhHdJx67cq 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 K|Gz_z6#jqO>p18D literal 44411 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

O-y@eZG7Y>gZ>g^bw^D6wl-p*~C^L@A{Fd6f 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 + + + + + + + + diff --git a/app/src/main/cpp/AssetManager.h b/app/src/main/cpp/AssetManager.h index 731ff0d..cf31634 100644 --- a/app/src/main/cpp/AssetManager.h +++ b/app/src/main/cpp/AssetManager.h @@ -66,7 +66,7 @@ protected: private: // FIXME(Kevin): Temporary workaround - std::map, FontData> m_font_cache; + std::map, FontData> m_font_cache; }; #endif diff --git a/app/src/main/cpp/Gfx.h b/app/src/main/cpp/Gfx.h new file mode 100644 index 0000000..1597669 --- /dev/null +++ b/app/src/main/cpp/Gfx.h @@ -0,0 +1,15 @@ +#ifndef KRIMI_DINNER_ENGINE_GFX_H +#define KRIMI_DINNER_ENGINE_GFX_H + + +struct KDERect +{ + float dst_p0_x, dst_p0_y; + float dst_p1_x, dst_p1_y; + float src_p0_x, src_p0_y; + float src_p1_x, src_p1_y; + float r, g, b, a; + int expand_r; +}; + +#endif diff --git a/app/src/main/cpp/OpenGLGFXInterface.cpp b/app/src/main/cpp/OpenGLGFXInterface.cpp new file mode 100644 index 0000000..54b7022 --- /dev/null +++ b/app/src/main/cpp/OpenGLGFXInterface.cpp @@ -0,0 +1,285 @@ +#include "OpenGLGfxInterface.h" +#include "Log.h" +#include "AssetManager.h" +#include "Texture.h" +#include "Profiling.h" + +#ifdef _WIN32 + #include "glad.h" +#elif defined(__ANDROID__) + #include +#else + #pragma error "Don't compile this!" +#endif +#include +#include + +#include + + +static const char* g_vert_src = "#version 300 es \n" + "precision mediump float; \n" + "uniform vec2 res; // resolution \n" + "layout (location = 0) in vec2 v_p0; // top left corner on screen \n" + "layout (location = 1) in vec2 v_p1; // top right corner on screen \n" + "layout (location = 2) in vec2 v_src_p0; \n" + "layout (location = 3) in vec2 v_src_p1; \n" + "layout (location = 4) in vec4 v_color; // color \n" + "layout (location = 5) in int v_expand_r; // \n" + "out vec4 f_color; \n" + "out vec2 f_uv; \n" + "flat out int f_expand_r; \n" + "const vec2 vertices[6] = vec2[6]( \n" + " vec2(-1,-1), \n" + " vec2(+1, -1), \n" + " vec2(-1, +1), \n" + " vec2(+1, -1), \n" + " vec2(+1, +1), \n" + " vec2(-1, +1) \n" + "); \n" + "void main() \n" + "{ \n" + " vec2 v = vertices[gl_VertexID]; \n" + " // destination on screen \n" + " //vec2 dst_half_size = (v_p1 - v_p0) / 2.0; \n" + " //vec2 dst_center = (v_p1 + v_p0) / 2.0; \n" + " vec2 dst_pos = v * ((v_p1 - v_p0) / 2.0) + ((v_p1 + v_p0) / 2.0); \n" + " dst_pos.y = res.y - dst_pos.y; \n" + " vec2 src = v * ((v_src_p1 - v_src_p0) / 2.0) + ((v_src_p1 + v_src_p0) / 2.0); \n" + " gl_Position = vec4(2.0 * dst_pos.x / res.x - 1.0, \n" + " 2.0 * dst_pos.y / res.y - 1.0, \n" + " 0.0, \n" + " 1.0); \n" + " f_color = v_color; \n" + " f_uv = src; \n" + " f_expand_r = v_expand_r; \n" + "} \n"; + +static const char* g_frag_src = "#version 300 es \n" + "precision mediump float; \n" + "in vec2 f_uv; \n" + "in vec4 f_color; \n" + "flat in int f_expand_r; \n" + "layout (location = 0) out vec4 frag_color; \n" + "uniform sampler2D s_texture; \n" + "void main() \n" + "{ \n" + " vec4 tex_color = texture(s_texture, f_uv); \n" + " if (f_expand_r == 1) \n" + " tex_color = tex_color.rrrr; \n" + " frag_color = tex_color * f_color; \n" + "} \n"; + +struct OpenGLGfx +{ + GLuint m_shader; + GLuint m_vao; + GLuint m_vbo; + + KDEStringHandle m_dummy_texture; + + std::vector m_rects; + std::vector m_draw_textures; + + std::unordered_map m_textures; +} + +OpenGLGfx* createOpenGLGfx(); +{ + ZoneScoped; + OpenGLGFX* gfx = new OpenGLGFX; + + GLuint vertex = 0, fragment = 0; + vertex = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vertex, 1, &g_vert_src, nullptr); + glCompileShader(vertex); + int status = 0; + glGetShaderiv(vertex, GL_COMPILE_STATUS, &status); + if (status != GL_TRUE) { + char info[512]; + glGetShaderInfoLog(vertex, 512, nullptr, info); + ALOGE("Failed to compile ui vertex shader: %s", info); + } + + fragment = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragment, 1, &g_frag_src, nullptr); + glCompileShader(fragment); + glGetShaderiv(fragment, GL_COMPILE_STATUS, &status); + if (status != GL_TRUE) { + char info[512]; + glGetShaderInfoLog(fragment, 512, nullptr, info); + ALOGE("Failed to compile ui fragment shader: %s", info); + } + + gfx->m_shader = glCreateProgram(); + glAttachShader(gfx->m_shader, vertex); + glAttachShader(gfx->m_shader, fragment); + glLinkProgram(gfx->m_shader); + glGetProgramiv(gfx->m_shader, GL_LINK_STATUS, &status); + if (status != GL_TRUE) { + char info[512]; + glGetProgramInfoLog(gfx->m_shader, 512, nullptr, info); + ALOGE("Failed to link ui shader: %s", info); + } + + glDeleteShader(vertex); + glDeleteShader(fragment); + + glGenBuffers(1, &gfx->m_vbo); + glGenVertexArrays(1, &gfx->m_vao); + glBindVertexArray(gfx->m_vao); + glBindBuffer(GL_ARRAY_BUFFER, gfx->m_vbo); + + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Rect), (GLvoid*)0); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Rect), (GLvoid*)offsetof(Rect, dst_p1_x)); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Rect), (GLvoid*)offsetof(Rect, src_p0_x)); + glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(Rect), (GLvoid*)offsetof(Rect, src_p1_x)); + glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(Rect), (GLvoid*)offsetof(Rect, r)); + glVertexAttribIPointer(5, 1, GL_INT, sizeof(Rect), (GLvoid*)offsetof(Rect, expand_r)); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + glEnableVertexAttribArray(3); + glEnableVertexAttribArray(4); + glEnableVertexAttribArray(5); + glVertexAttribDivisor(0, 1); + glVertexAttribDivisor(1, 1); + glVertexAttribDivisor(2, 1); + glVertexAttribDivisor(3, 1); + glVertexAttribDivisor(4, 1); + glVertexAttribDivisor(5, 1); + glBindVertexArray(0); + + // Dummy texture (1x1px white) + unsigned char tex[] = {255, 255, 255, 255}; + Texture dummy_texture(1, 1, tex); + gfx->m_textures.insert(std::make_pair(0, dummy_texture)); + m_dummy_texture = 0; +} + +void destroyOpenGLGfx(OpenGLGFX* gfx) +{ + ZoneScoped; + for (auto texture : gfx->m_textures) + texture.second.destroy(); + glDeleteProgram(gfx->m_shader); + glDeleteVertexArrays(1, &gfx->m_vao); + glDeleteBuffers(1, &gfx->m_vbo); + + delete gfx; +} + +void openGLGfxPushRect(OpenGLGfx* gfx, KDERect rect, KDEStringHandle texture) +{ + if (gfx->m_textures.find(texture) == gfx->m_textures.end()) { + Texture tex; + if (!AssetManager::ptr->loadTexture(StringRepository::global->getString(texture), &tex)) { + ALOGE("Failed to load texture"); + return; + } + gfx->m_textures.insert(std::make_pair(texture, tex)); + } + + gfx->m_rects.push_back(rect); + gfx->m_draw_textures.push_back(texture); +} + + +void openGLGfxPushFontRect(OpenGLGfx* gfx, KDERect rect, + float char_height, + KDEStringHandle font, + KDEStringHandle id) +{ + if (gfx->m_textures.find(id) == gfx->m_textures.end()) { + Texture tex; + if (!AssetManager::ptr->loadFontBitmap(StringRepository::global->getString(font), char_height, &tex)) { + ALOGE("Failed to load font"); + return; + } + gfx->m_textures.insert(std::make_pair(id, tex)); + } + + gfx->m_rects.push_back(rect); + gfx->m_draw_textures.push_back(texture); +} + + +void openGLGetTextureSize(OpenGLGfx* gfx, KDEStringHandle texture, unsigned int* w, unsigned int* h) +{ + ZoneScoped; + if (gfx->m_textures.find(texture) == gfx->m_textures.end()) { + Texture tex; + if (!AssetManager::ptr->loadTexture(StringRepository::global->getString(texture), &tex)) { + ALOGE("Failed to load texture"); + return; + } + gfx->m_textures.insert(std::make_pair(texture, tex)); + } + Texture tex = gfx->m_textures[texture]; + tex.getTextureSize(w, h); +} + +void openGLRenderFrame(OpenGLGfx* gfx, float width, float height) +{ + ZoneScoped; + assert(gfx->m_rects.size() == gfx->m_draw_textures.size()); + + glViewport(0, 0, static_cast(width), static_cast(height)); + // glClearColor(0.8f, 0.3f, 0.3f, 1.f); + glClearColor(0.0f, 0.0f, 0.0f, 1.f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + const auto rect_count = static_cast(gfx->m_rects.size()); + if (rect_count == 0) + return; + + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glBindVertexArray(gfx->m_vao); + glUseProgram(gfx->m_shader); + glUniform2f(glGetUniformLocation(gfx->m_shader, "res"), width, height); + glUniform1i(glGetUniformLocation(gfx->m_shader, "s_texture"), 0); + glBindBuffer(GL_ARRAY_BUFFER, gfx->m_vbo); + + size_t start = 0; + StringHandle current_texture = gfx->m_draw_textures[0]; + while (start < rect_count) { + glActiveTexture(GL_TEXTURE0); + gfx->m_textures[current_texture].bind(); + for (size_t i = start; i < rect_count; ++i) { + StringHandle texture = gfx->m_draw_textures[i]; + if ((texture != current_texture)) { + // Draw everything in [start, i - 1] + const size_t count = i - start; + glBufferData(GL_ARRAY_BUFFER, + static_cast(sizeof(Rect) * count), + &gfx->m_rects[start], + GL_STREAM_DRAW); + glDrawArraysInstanced(GL_TRIANGLES, 0, 6, static_cast(count)); + + start = i; + current_texture = texture; + break; + } + + if (i == rect_count - 1) { + // just draw it + const size_t count = rect_count - start; + glBufferData(GL_ARRAY_BUFFER, + static_cast(sizeof(Rect) * count), + &gfx->m_rects[start], + GL_STREAM_DRAW); + glDrawArraysInstanced(GL_TRIANGLES, 0, 6, static_cast(count)); + + start = rect_count; + current_texture = texture; + } + } + } + gfx->m_rects.clear(); + gfx->m_draw_textures.clear(); +} diff --git a/app/src/main/cpp/OpenGLGFXInterface.h b/app/src/main/cpp/OpenGLGFXInterface.h new file mode 100644 index 0000000..8bce7d4 --- /dev/null +++ b/app/src/main/cpp/OpenGLGFXInterface.h @@ -0,0 +1,25 @@ +#ifndef KRIMI_DINNER_GFX_INTERFACE_H +#define KRIMI_DINNER_GFX_INTERFACE_H + +#include "Gfx.h" +#include "StringRepository.h" + +struct OpenGLGfx; + +OpenGLGfx* createOpenGLGfx(); + +void destroyOpenGLGfx(OpenGLGfx* gfx); + +void openGLGfxPushRect(OpenGLGfx* gfx, KDERect rect, KDEStringHandle texture); + +void openGLGfxPushFontRect(OpenGLGfx* gfx, KDERect rect, + float char_height, + KDEStringHandle font, + KDEStringHandle id); + +void openGLRenderFrame(OpenGLGfx* gfx, float width, float height); + +void openGLGetTextureSize(OpenGLGfx* gfx, KDEStringHandle texture, unsigned int* w, unsigned int* h); + +#endif + diff --git a/app/src/main/cpp/Renderer.cpp b/app/src/main/cpp/Renderer.cpp index 9875f18..d1bc815 100644 --- a/app/src/main/cpp/Renderer.cpp +++ b/app/src/main/cpp/Renderer.cpp @@ -1,70 +1,22 @@ #include "Renderer.h" -#include "Log.h" -#include "AssetManager.h" -#include "Texture.h" #include "Profiling.h" +#ifdef __APPLE__ +#include "../m/MetalGfxInterface.h" +#else +#include "OpenGLGFXInterface.h" +#endif + #include Renderer* Renderer::ptr = nullptr; -static const char* g_vert_src = "#version 300 es \n" - "precision mediump float; \n" - "uniform vec2 res; // resolution \n" - "layout (location = 0) in vec2 v_p0; // top left corner on screen \n" - "layout (location = 1) in vec2 v_p1; // top right corner on screen \n" - "layout (location = 2) in vec2 v_src_p0; \n" - "layout (location = 3) in vec2 v_src_p1; \n" - "layout (location = 4) in vec4 v_color; // color \n" - "layout (location = 5) in int v_expand_r; // \n" - "out vec4 f_color; \n" - "out vec2 f_uv; \n" - "flat out int f_expand_r; \n" - "const vec2 vertices[6] = vec2[6]( \n" - " vec2(-1,-1), \n" - " vec2(+1, -1), \n" - " vec2(-1, +1), \n" - " vec2(+1, -1), \n" - " vec2(+1, +1), \n" - " vec2(-1, +1) \n" - "); \n" - "void main() \n" - "{ \n" - " vec2 v = vertices[gl_VertexID]; \n" - " // destination on screen \n" - " //vec2 dst_half_size = (v_p1 - v_p0) / 2.0; \n" - " //vec2 dst_center = (v_p1 + v_p0) / 2.0; \n" - " vec2 dst_pos = v * ((v_p1 - v_p0) / 2.0) + ((v_p1 + v_p0) / 2.0); \n" - " dst_pos.y = res.y - dst_pos.y; \n" - " vec2 src = v * ((v_src_p1 - v_src_p0) / 2.0) + ((v_src_p1 + v_src_p0) / 2.0); \n" - " gl_Position = vec4(2.0 * dst_pos.x / res.x - 1.0, \n" - " 2.0 * dst_pos.y / res.y - 1.0, \n" - " 0.0, \n" - " 1.0); \n" - " f_color = v_color; \n" - " f_uv = src; \n" - " f_expand_r = v_expand_r; \n" - "} \n"; +#define DUMMY_TEXTURE 0 -static const char* g_frag_src = "#version 300 es \n" - "precision mediump float; \n" - "in vec2 f_uv; \n" - "in vec4 f_color; \n" - "flat in int f_expand_r; \n" - "layout (location = 0) out vec4 frag_color; \n" - "uniform sampler2D s_texture; \n" - "void main() \n" - "{ \n" - " vec4 tex_color = texture(s_texture, f_uv); \n" - " if (f_expand_r == 1) \n" - " tex_color = tex_color.rrrr; \n" - " frag_color = tex_color * f_color; \n" - "} \n"; - -void Renderer::create() +void Renderer::create(void* gfx) { ZoneScoped; - Renderer::ptr = new Renderer; + Renderer::ptr = new Renderer(gfx); } void Renderer::destroy() @@ -74,94 +26,24 @@ void Renderer::destroy() Renderer::ptr = nullptr; } -Renderer::Renderer() +Renderer::Renderer(void* gfx) + : m_gfx(gfx) { ZoneScoped; - GLuint vertex = 0, fragment = 0; - vertex = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(vertex, 1, &g_vert_src, nullptr); - glCompileShader(vertex); - int status = 0; - glGetShaderiv(vertex, GL_COMPILE_STATUS, &status); - if (status != GL_TRUE) { - char info[512]; - glGetShaderInfoLog(vertex, 512, nullptr, info); - ALOGE("Failed to compile ui vertex shader: %s", info); - } - - fragment = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(fragment, 1, &g_frag_src, nullptr); - glCompileShader(fragment); - glGetShaderiv(fragment, GL_COMPILE_STATUS, &status); - if (status != GL_TRUE) { - char info[512]; - glGetShaderInfoLog(fragment, 512, nullptr, info); - ALOGE("Failed to compile ui fragment shader: %s", info); - } - - m_shader = glCreateProgram(); - glAttachShader(m_shader, vertex); - glAttachShader(m_shader, fragment); - glLinkProgram(m_shader); - glGetProgramiv(m_shader, GL_LINK_STATUS, &status); - if (status != GL_TRUE) { - char info[512]; - glGetProgramInfoLog(m_shader, 512, nullptr, info); - ALOGE("Failed to link ui shader: %s", info); - } - - glDeleteShader(vertex); - glDeleteShader(fragment); - - glGenBuffers(1, &m_vbo); - glGenVertexArrays(1, &m_vao); - glBindVertexArray(m_vao); - glBindBuffer(GL_ARRAY_BUFFER, m_vbo); - - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Rect), (GLvoid*)0); - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Rect), (GLvoid*)offsetof(Rect, dst_p1_x)); - glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Rect), (GLvoid*)offsetof(Rect, src_p0_x)); - glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(Rect), (GLvoid*)offsetof(Rect, src_p1_x)); - glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(Rect), (GLvoid*)offsetof(Rect, r)); - glVertexAttribIPointer(5, 1, GL_INT, sizeof(Rect), (GLvoid*)offsetof(Rect, expand_r)); - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); - glEnableVertexAttribArray(2); - glEnableVertexAttribArray(3); - glEnableVertexAttribArray(4); - glEnableVertexAttribArray(5); - glVertexAttribDivisor(0, 1); - glVertexAttribDivisor(1, 1); - glVertexAttribDivisor(2, 1); - glVertexAttribDivisor(3, 1); - glVertexAttribDivisor(4, 1); - glVertexAttribDivisor(5, 1); - glBindVertexArray(0); - - // Dummy texture (1x1px white) - unsigned char tex[] = {255, 255, 255, 255}; - Texture dummy_texture(1, 1, tex); - m_textures.insert(std::make_pair(0, dummy_texture)); - m_dummy_texture = 0; } Renderer::~Renderer() { ZoneScoped; - for (auto texture : m_textures) - texture.second.destroy(); - glDeleteProgram(m_shader); - glDeleteVertexArrays(1, &m_vao); - glDeleteBuffers(1, &m_vbo); } void Renderer::addRect(float x, float y, float w, float h) { ZoneScoped; - addRect(x, y, w, h, 1.f, 1.f, 1.f, 1.f, m_dummy_texture, 0.f, 0.f, 1.f, 1.f); + addRect(x, y, w, h, 1.f, 1.f, 1.f, 1.f, DUMMY_TEXTURE, 0.f, 0.f, 1.f, 1.f); } -void Renderer::addRect(float x, float y, float w, float h, StringHandle texture) +void Renderer::addRect(float x, float y, float w, float h, KDEStringHandle texture) { ZoneScoped; addRect(x, y, w, h, 1.f, 1.f, 1.f, 1.f, texture, 0.f, 0.f, 1.f, 1.f); @@ -170,10 +52,10 @@ void Renderer::addRect(float x, float y, float w, float h, StringHandle texture) void Renderer::addRect(float x, float y, float w, float h, float r, float g, float b, float a) { ZoneScoped; - addRect(x, y, w, h, r, g, b, a, m_dummy_texture, 0.f, 0.f, 1.f, 1.f); + addRect(x, y, w, h, r, g, b, a, DUMMY_TEXTURE, 0.f, 0.f, 1.f, 1.f); } -void Renderer::addRect(float x, float y, float w, float h, float r, float g, float b, float a, StringHandle texture) +void Renderer::addRect(float x, float y, float w, float h, float r, float g, float b, float a, KDEStringHandle texture) { ZoneScoped; addRect(x, y, w, h, r, g, b, a, texture, 0.f, 0.f, 1.f, 1.f); @@ -183,7 +65,7 @@ void Renderer::addRect(float x, float y, float w, float h, - StringHandle texture, + KDEStringHandle texture, float src_x, float src_y, float src_w, @@ -201,23 +83,15 @@ void Renderer::addRect(float x, float g, float b, float a, - StringHandle texture, + KDEStringHandle texture, float src_x, float src_y, float src_w, float src_h) { ZoneScoped; - if (m_textures.find(texture) == m_textures.end()) { - Texture tex; - if (!AssetManager::ptr->loadTexture(StringRepository::global->getString(texture), &tex)) { - ALOGE("Failed to load texture"); - return; - } - m_textures.insert(std::make_pair(texture, tex)); - } - Rect rect = {}; + KDERect rect = {}; rect.dst_p0_x = x; rect.dst_p0_y = y; rect.dst_p1_x = x + w; @@ -231,8 +105,12 @@ void Renderer::addRect(float x, rect.b = b; rect.a = a; rect.expand_r = 0; - m_rects.push_back(rect); - m_draw_textures.push_back(texture); + +#ifdef __APPLE__ + metalGfxPushRect(m_gfx, rect, texture); +#else + openGLGfxPushRect((OpenGLGfx*)m_gfx, rect, texture); +#endif } void Renderer::addFontRect(float x, @@ -240,8 +118,8 @@ void Renderer::addFontRect(float x, float w, float h, float char_height, - StringHandle font, - StringHandle id, + KDEStringHandle font, + KDEStringHandle id, float src_x0, float src_y0, float src_x1, @@ -260,24 +138,15 @@ void Renderer::addFontRect(float x, float b, float a, float char_height, - StringHandle font, - StringHandle id, + KDEStringHandle font, + KDEStringHandle id, float src_x0, float src_y0, float src_x1, float src_y1) { ZoneScoped; - if (m_textures.find(id) == m_textures.end()) { - Texture tex; - if (!AssetManager::ptr->loadFontBitmap(StringRepository::global->getString(font), char_height, &tex)) { - ALOGE("Failed to load texture"); - return; - } - m_textures.insert(std::make_pair(id, tex)); - } - - Rect rect = {}; + KDERect rect = {}; rect.dst_p0_x = x; rect.dst_p0_y = y; rect.dst_p1_x = x + w; @@ -291,86 +160,27 @@ void Renderer::addFontRect(float x, rect.b = b; rect.a = a; rect.expand_r = 1; - m_rects.push_back(rect); - m_draw_textures.push_back(id); + + +#ifdef __APPLE__ + metalGfxPushFontRect(m_gfx, rect, char_height, font, id); +#else + openGLGfxPushFontRect((OpenGLGfx*)m_gfx, rect, char_height, font, id); +#endif } void Renderer::renderFrame(float width, float height) { - ZoneScoped; - assert(m_rects.size() == m_draw_textures.size()); - - glViewport(0, 0, static_cast(width), static_cast(height)); - // glClearColor(0.8f, 0.3f, 0.3f, 1.f); - glClearColor(0.0f, 0.0f, 0.0f, 1.f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - const auto rect_count = static_cast(m_rects.size()); - if (rect_count == 0) - return; - - glDisable(GL_DEPTH_TEST); - glDisable(GL_CULL_FACE); - glEnable(GL_BLEND); - glBlendEquation(GL_FUNC_ADD); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - glBindVertexArray(m_vao); - glUseProgram(m_shader); - glUniform2f(glGetUniformLocation(m_shader, "res"), width, height); - glUniform1i(glGetUniformLocation(m_shader, "s_texture"), 0); - glBindBuffer(GL_ARRAY_BUFFER, m_vbo); - - size_t start = 0; - StringHandle current_texture = m_draw_textures[0]; - while (start < rect_count) { - glActiveTexture(GL_TEXTURE0); - m_textures[current_texture].bind(); - for (size_t i = start; i < rect_count; ++i) { - StringHandle texture = m_draw_textures[i]; - if ((texture != current_texture)) { - // Draw everything in [start, i - 1] - const size_t count = i - start; - glBufferData(GL_ARRAY_BUFFER, - static_cast(sizeof(Rect) * count), - &m_rects[start], - GL_STREAM_DRAW); - glDrawArraysInstanced(GL_TRIANGLES, 0, 6, static_cast(count)); - - start = i; - current_texture = texture; - break; - } - - if (i == rect_count - 1) { - // just draw it - const size_t count = rect_count - start; - glBufferData(GL_ARRAY_BUFFER, - static_cast(sizeof(Rect) * count), - &m_rects[start], - GL_STREAM_DRAW); - glDrawArraysInstanced(GL_TRIANGLES, 0, 6, static_cast(count)); - - start = rect_count; - current_texture = texture; - } - } - } - m_rects.clear(); - m_draw_textures.clear(); +#if defined(_WIN32) || defined(__ANDROID__) + openGLGfxRenderFrame((OpenGLGfx*)m_gfx, width, height); +#endif } -void Renderer::getTextureSize(StringHandle texture, unsigned int* w, unsigned int* h) +void Renderer::getTextureSize(KDEStringHandle texture, unsigned int* w, unsigned int* h) { - ZoneScoped; - if (m_textures.find(texture) == m_textures.end()) { - Texture tex; - if (!AssetManager::ptr->loadTexture(StringRepository::global->getString(texture), &tex)) { - ALOGE("Failed to load texture"); - return; - } - m_textures.insert(std::make_pair(texture, tex)); - } - Texture tex = m_textures[texture]; - tex.getTextureSize(w, h); -} \ No newline at end of file +#ifdef __APPLE__ + +#else + openGLGfxGetTextureSize((OpenGLGfx*)m_gfx, texture, w, h); +#endif +} diff --git a/app/src/main/cpp/Renderer.h b/app/src/main/cpp/Renderer.h index 7a16d58..a45ef14 100644 --- a/app/src/main/cpp/Renderer.h +++ b/app/src/main/cpp/Renderer.h @@ -2,15 +2,8 @@ #define KRIMI_DINNER_ENGINE_RENDERER_H /// @file Renderer.h -/// @brief GLES3 renderer +/// @brief Platform independent renderer interface -#ifdef _WIN32 - #include "glad.h" -#else - #include -#endif -#include -#include #include "Texture.h" #include "StringRepository.h" @@ -23,7 +16,7 @@ public: static Renderer* ptr; /// @brief Creates the singleton and sets @ref Renderer::ptr - static void create(); + static void create(void* gfx); /// @brief Destroys the singleton, only call this if you know what you are doing. static void destroy(); @@ -41,7 +34,7 @@ public: /// \param w width in pixels /// \param h height in pixels /// \param texture String handle in the global string repository of the texture file name - void addRect(float x, float y, float w, float h, StringHandle texture); + void addRect(float x, float y, float w, float h, KDEStringHandle texture); /// @brief Adds a colored rectangle to the screen /// \param x X-coordinate of the top-left corner @@ -64,7 +57,7 @@ public: /// \param b blue [0,1] /// \param a alpha [0,1] /// \param texture String handle in the global string repository of the texture file name - void addRect(float x, float y, float w, float h, float r, float g, float b, float a, StringHandle texture); + void addRect(float x, float y, float w, float h, float r, float g, float b, float a, KDEStringHandle texture); /// @brief Adds a textured rectangle to the screen /// \param x X-coordinate of the top-left corner @@ -80,7 +73,7 @@ public: float y, float w, float h, - StringHandle texture, + KDEStringHandle texture, float src_x, float src_y, float src_w, @@ -108,7 +101,7 @@ public: float g, float b, float a, - StringHandle texture, + KDEStringHandle texture, float src_x, float src_y, float src_w, @@ -130,8 +123,8 @@ public: float w, float h, float char_height, - StringHandle font, - StringHandle id, + KDEStringHandle font, + KDEStringHandle id, float src_x0, float src_y0, float src_x1, @@ -161,8 +154,8 @@ public: float b, float a, float char_height, - StringHandle font, - StringHandle id, + KDEStringHandle font, + KDEStringHandle id, float src_x0, float src_y0, float src_x1, @@ -179,37 +172,18 @@ public: /// \param w output width in pixels /// \param h output height in pixels void getTextureSize( - StringHandle texture, + KDEStringHandle texture, unsigned int* w, unsigned int* h); private: - Renderer(); + Renderer(void* gfx); ~Renderer(); Renderer(const Renderer&) = delete; Renderer& operator=(const Renderer&) = delete; - - struct Rect - { - float dst_p0_x, dst_p0_y; - float dst_p1_x, dst_p1_y; - float src_p0_x, src_p0_y; - float src_p1_x, src_p1_y; - float r, g, b, a; - int expand_r; - }; - - GLuint m_shader; - GLuint m_vao; - GLuint m_vbo; - - StringHandle m_dummy_texture; - - std::vector m_rects; - std::vector m_draw_textures; - - std::unordered_map m_textures; + + void *m_gfx; }; -#endif \ No newline at end of file +#endif diff --git a/app/src/main/cpp/StringRepository.cpp b/app/src/main/cpp/StringRepository.cpp index ec9a6dc..36112a5 100644 --- a/app/src/main/cpp/StringRepository.cpp +++ b/app/src/main/cpp/StringRepository.cpp @@ -40,7 +40,7 @@ StringRepository::~StringRepository() free(m_buffer); } -StringHandle StringRepository::internStringLength(const char* string, unsigned int length) +KDEStringHandle StringRepository::internStringLength(const char* string, unsigned int length) { ZoneScoped; uint64_t hash = hashString(string, length); @@ -62,16 +62,16 @@ StringHandle StringRepository::internStringLength(const char* string, unsigned i if (!m_hash.insert(hash, offset)) return 0; } - return static_cast(offset + 1); + return static_cast(offset + 1); } -StringHandle StringRepository::internString(const char* string) +KDEStringHandle StringRepository::internString(const char* string) { ZoneScoped; return internStringLength(string, strlen(string)); } -const char* StringRepository::getString(StringHandle handle) +const char* StringRepository::getString(KDEStringHandle handle) { ZoneScoped; if (handle == 0 || handle >= m_one_past_last_char) @@ -80,13 +80,13 @@ const char* StringRepository::getString(StringHandle handle) return &m_buffer[offset]; } -void StringRepository::releaseString(StringHandle) +void StringRepository::releaseString(KDEStringHandle) { // do nothing } -StringHandle StringRepository::concatenateString(StringHandle a, StringHandle b) +KDEStringHandle StringRepository::concatenateString(KDEStringHandle a, KDEStringHandle b) { ZoneScoped; const char* texta = getString(a); diff --git a/app/src/main/cpp/StringRepository.h b/app/src/main/cpp/StringRepository.h index 87d00df..f61700c 100644 --- a/app/src/main/cpp/StringRepository.h +++ b/app/src/main/cpp/StringRepository.h @@ -8,10 +8,10 @@ #include "Hash.h" /// A string handle -using StringHandle = uint32_t; +using KDEStringHandle = uint32_t; // Alias for compatibility -using nt_string_handle = StringHandle; +using nt_string_handle = KDEStringHandle; /// @brief Stores strings class StringRepository @@ -27,24 +27,24 @@ public: /// @brief Adds a string to the string repository /// @param string the string /// @param length length in bytes - StringHandle internStringLength(const char* string, unsigned int length); + KDEStringHandle internStringLength(const char* string, unsigned int length); /// @brief Adds a string to the string repository /// @param string the string - StringHandle internString(const char* string); + KDEStringHandle internString(const char* string); /// @brief Retrieves a string value /// @param handle The string handle /// @return The string - const char* getString(StringHandle handle); + const char* getString(KDEStringHandle handle); /// @brief Frees a string - void releaseString(StringHandle handle); + void releaseString(KDEStringHandle handle); /// @brief Adds a string to the string repository /// @param a the first string /// @param b the second string - StringHandle concatenateString(StringHandle a, StringHandle b); + KDEStringHandle concatenateString(KDEStringHandle a, KDEStringHandle b); /// @brief Frees all strings inside the repository void freeAll(); @@ -56,4 +56,4 @@ private: Hash m_hash; }; -#endif \ No newline at end of file +#endif diff --git a/app/src/main/m/GameViewController.m b/app/src/main/m/GameViewController.mm similarity index 72% rename from app/src/main/m/GameViewController.m rename to app/src/main/m/GameViewController.mm index 924b187..9656c42 100644 --- a/app/src/main/m/GameViewController.m +++ b/app/src/main/m/GameViewController.mm @@ -6,36 +6,40 @@ // #import "GameViewController.h" -#import "Renderer.h" +#import "MetalViewDelegate.h" + +#include "../cpp/Renderer.h" @implementation GameViewController { MTKView *_view; - Renderer *_renderer; + MetalViewDelegate *_renderer; } - (void)viewDidLoad { [super viewDidLoad]; - + _view = (MTKView *)self.view; - + _view.device = MTLCreateSystemDefaultDevice(); _view.backgroundColor = UIColor.blackColor; - + if(!_view.device) { NSLog(@"Metal is not supported on this device"); self.view = [[UIView alloc] initWithFrame:self.view.frame]; return; } - - _renderer = [[Renderer alloc] initWithMetalKitView:_view]; - + + _renderer = [[MetalViewDelegate alloc] initWithMetalKitView:_view]; + [_renderer mtkView:_view drawableSizeWillChange:_view.bounds.size]; - + _view.delegate = _renderer; + + Renderer::create((__bridge void*)_renderer); } @end diff --git a/app/src/main/m/MetalGfxInterface.h b/app/src/main/m/MetalGfxInterface.h new file mode 100644 index 0000000..1a1b4d1 --- /dev/null +++ b/app/src/main/m/MetalGfxInterface.h @@ -0,0 +1,11 @@ +#ifndef KRIMI_DINNER_ENGINE_METAL_GFX_INTERFACE_H +#define KRIMI_DINNER_ENGINE_METAL_GFX_INTERFACE_H + +#include "../cpp/Gfx.h" +#include "../cpp/StringRepository.h" + +void metalGfxPushRect(void* metalGfxObj, KDERect rect, KDEStringHandle texture); + +void metalGfxPushFontRect(void* metalGfxObj, KDERect rect, float char_height, KDEStringHandle font, KDEStringHandle key); + +#endif diff --git a/app/src/main/m/MetalGfxInterface.mm b/app/src/main/m/MetalGfxInterface.mm new file mode 100644 index 0000000..e5a220a --- /dev/null +++ b/app/src/main/m/MetalGfxInterface.mm @@ -0,0 +1,12 @@ +#import +#import "MetalViewDelegate.h" + +void metalGfxPushRect(void* metalGfxObj, KDERect rect, KDEStringHandle texture) +{ + [(__bridge id) metalGfxObj pushRect: rect withTexture: texture]; +} + +void metalGfxPushFontRect(void* metalGfxObj, KDERect rect, float char_height, KDEStringHandle font, KDEStringHandle key) +{ + [(__bridge id)metalGfxObj pushRect:rect withFont:font withCharHeight:char_height withKey:key]; +} diff --git a/app/src/main/m/Renderer.h b/app/src/main/m/MetalViewDelegate.h similarity index 52% rename from app/src/main/m/Renderer.h rename to app/src/main/m/MetalViewDelegate.h index 30ac437..7b5fc96 100644 --- a/app/src/main/m/Renderer.h +++ b/app/src/main/m/MetalViewDelegate.h @@ -7,11 +7,18 @@ #import +#include "../cpp/Gfx.h" +#include "../cpp/StringRepository.h" + // Our platform independent renderer class. Implements the MTKViewDelegate protocol which // allows it to accept per-frame update and drawable resize callbacks. -@interface Renderer : NSObject +@interface MetalViewDelegate : NSObject -(nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)view; + +-(void)pushRect:(KDERect)rect withTexture:(KDEStringHandle)texture; + +-(void)pushRect:(KDERect)rect withFont:(KDEStringHandle)font withCharHeight:(float)char_height withKey:(KDEStringHandle)key; @end diff --git a/app/src/main/m/Renderer.m b/app/src/main/m/MetalViewDelegate.mm similarity index 93% rename from app/src/main/m/Renderer.m rename to app/src/main/m/MetalViewDelegate.mm index dd50ba9..886a894 100644 --- a/app/src/main/m/Renderer.m +++ b/app/src/main/m/MetalViewDelegate.mm @@ -8,14 +8,16 @@ #import #import -#import "Renderer.h" +#import "MetalViewDelegate.h" // Include header shared between C code here, which executes Metal API commands, and .metal files #import "ShaderTypes.h" +#include "../cpp/Renderer.h" + static const NSUInteger MaxBuffersInFlight = 3; -@implementation Renderer +@implementation MetalViewDelegate { dispatch_semaphore_t _inFlightSemaphore; id _device; @@ -169,8 +171,27 @@ static const NSUInteger MaxBuffersInFlight = 3; - (void)_updateGameState { - /// Update any game state before encoding renderint commands to our drawable + // Demo code + static float x = 1.f; + static float d = -0.01f; + x += d; + if (x <= 0.f) + d *= -1.f; + else if (x >= 1.f) + d *= -1.f; +#if 0 + if (input_event_count > 0) { + m_smiley_pos = input_events[input_event_count - 1].end; + m_smiley_pos.x -= 250; + m_smiley_pos.y -= 250; + } +#endif + + Renderer::ptr->addRect(100, 100, 500, 500, 0.3f, 0.3f, 0.3f, 1.f); + //Renderer::ptr->addRect(m_smiley_pos.x, m_smiley_pos.y, 500, 500, 0.f, x * x, 1.f - x * x, 1.f, m_smiley); + + /// Update any game state before encoding renderint commands to our drawable Uniforms * uniforms = (Uniforms*)_dynamicUniformBuffer[_uniformBufferIndex].contents; uniforms->projectionMatrix = _projectionMatrix; @@ -271,6 +292,17 @@ static const NSUInteger MaxBuffersInFlight = 3; _projectionMatrix = matrix_perspective_right_hand(65.0f * (M_PI / 180.0f), aspect, 0.1f, 100.0f); } +- (void)pushRect:(KDERect)rect withTexture:(KDEStringHandle)texture +{ + // TODO +} + + +- (void)pushRect:(KDERect)rect withFont:(KDEStringHandle)font withCharHeight:(float)char_height withKey:(KDEStringHandle)key +{ + // TODO +} + #pragma mark Matrix Math Utilities matrix_float4x4 matrix4x4_translation(float tx, float ty, float tz)