From 4194764d7a174f6ecd1dcdfd95a6393bf8acadd3 Mon Sep 17 00:00:00 2001 From: OusmBlueNinja <89956790+OusmBlueNinja@users.noreply.github.com> Date: Wed, 21 May 2025 21:55:38 -0500 Subject: [PATCH] Started On Renderer --- src/core/Core.cpp | 19 +++- src/core/renderer/Renderer.cpp | 163 ++++++++++++++++++++++++++++- src/core/renderer/Renderer.h | 81 +++++++++++--- src/core/systems/Shader.cpp | 180 ++++++++++++++++++++++++++++++++ src/core/systems/Shader.h | 61 +++++++++++ src/editor/Windows/Viewport.cpp | 7 +- 6 files changed, 484 insertions(+), 27 deletions(-) create mode 100644 src/core/systems/Shader.cpp create mode 100644 src/core/systems/Shader.h diff --git a/src/core/Core.cpp b/src/core/Core.cpp index 6aca208..2eaadbb 100644 --- a/src/core/Core.cpp +++ b/src/core/Core.cpp @@ -6,6 +6,7 @@ #include "systems/MACROS.h" #include "systems/AssetManager.h" +#include "systems/assets/Texture2D.h" namespace OX { @@ -32,9 +33,7 @@ namespace OX } - - for (const auto &layer : m_layers) { - + for (const auto &layer: m_layers) { Logger::LogDebug("Initializing Layer: '%s'", layer->GetName().c_str()); layer->Init(*this); } @@ -42,6 +41,7 @@ namespace OX std::string fullTitle = layerTitle + " - " + m_name; window.SetWindowTitle(fullTitle); + renderer.Init(800, 600); Logger::LogOk("Core Initialization Complete."); @@ -49,7 +49,6 @@ namespace OX } - void Core::Run() { m_running = true; @@ -80,6 +79,18 @@ namespace OX { OX_PROFILE_FUNCTION(); + Camera2D cam(0, 800, 0, 600); + renderer.BeginScene(cam); + + if (auto asset = AssetManager::Get("res://Assets/tile_001.png"); asset && asset->GetTypeName() == "texture2D") { + const auto tex = std::static_pointer_cast(asset); + renderer.DrawSprite({tex->GetID(), {100, 100}, {64, 64}}); + } + + + + renderer.EndScene(); + for (auto &layer: m_layers) { layer->Draw(*this); diff --git a/src/core/renderer/Renderer.cpp b/src/core/renderer/Renderer.cpp index 474b9f5..f5c4434 100644 --- a/src/core/renderer/Renderer.cpp +++ b/src/core/renderer/Renderer.cpp @@ -1,9 +1,162 @@ -// -// Created by spenc on 5/21/2025. -// - +// File: src/Renderer.cpp #include "Renderer.h" +#include +#include namespace OX { -} // OX \ No newline at end of file +// ——— Camera2D implementation (unchanged) ——— +Camera2D::Camera2D(float left, float right, float bottom, float top) + : _left(left), _right(right), _bottom(bottom), _top(top) +{ Recalculate(); } + +void Camera2D::SetPosition(const glm::vec2& pos) { _position = pos; Recalculate(); } +void Camera2D::SetZoom(float z) { _zoom = z; Recalculate(); } + +void Camera2D::Recalculate() { + float halfW = (_right - _left) * 0.5f / _zoom; + float halfH = (_top - _bottom) * 0.5f / _zoom; + glm::mat4 proj = glm::ortho(-halfW, halfW, -halfH, halfH, -1.0f, 1.0f); + glm::mat4 view = glm::translate(glm::mat4(1.0f), glm::vec3(-_position, 0.0f)); + _viewProj = proj * view; +} + +const glm::mat4& Camera2D::GetViewProjection() const { return _viewProj; } + +// ——— Renderer ——— +Renderer::~Renderer() { + if (m_quadVAO) glDeleteVertexArrays(1, &m_quadVAO); + if (m_quadVBO) glDeleteBuffers(1, &m_quadVBO); + if (m_fbo) glDeleteFramebuffers(1, &m_fbo); + if (m_colorTex) glDeleteTextures(1, &m_colorTex); + if (m_depthRBO) glDeleteRenderbuffers(1, &m_depthRBO); +} + +void Renderer::Init(int targetWidth, int targetHeight) { + // create offscreen FBO & attachments + CreateFramebuffer(targetWidth, targetHeight); + + // compile our sprite shader + static const char* vs = R"GLSL( + #version 330 core + layout(location = 0) in vec2 aPos; + layout(location = 1) in vec2 aUV; + uniform mat4 u_ViewProj; + uniform vec2 u_Offset; + uniform vec2 u_Scale; + out vec2 vUV; + void main(){ + vUV = aUV; + vec2 pos = aPos * u_Scale + u_Offset; + gl_Position = u_ViewProj * vec4(pos,0,1); + } + )GLSL"; + static const char* fs = R"GLSL( + #version 330 core + in vec2 vUV; + out vec4 FragColor; + uniform sampler2D u_Texture; + void main(){ + FragColor = texture(u_Texture, vUV); + } + )GLSL"; + + m_shader.LoadFromSource(vs, fs); + + CreateQuad(); +} + +void Renderer::CreateFramebuffer(int width, int height) { + // cleanup old + if (m_fbo) glDeleteFramebuffers(1, &m_fbo); + if (m_colorTex) glDeleteTextures(1, &m_colorTex); + if (m_depthRBO) glDeleteRenderbuffers(1, &m_depthRBO); + + // color texture + glGenTextures(1, &m_colorTex); + glBindTexture(GL_TEXTURE_2D, m_colorTex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, + 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // depth renderbuffer + glGenRenderbuffers(1, &m_depthRBO); + glBindRenderbuffer(GL_RENDERBUFFER, m_depthRBO); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height); + + // framebuffer + glGenFramebuffers(1, &m_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, m_colorTex, 0); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, m_depthRBO); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + std::cerr << "[Renderer] Framebuffer not complete!\n"; + + // unbind + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +void Renderer::ResizeTarget(int width, int height) { + CreateFramebuffer(width, height); +} + +void Renderer::BeginScene(const Camera2D& camera) { + // bind offscreen + glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); + // clear + glViewport(0, 0, /*width*/0, /*height*/0); // you may want to track size + glClearColor(0,0,0,1); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // setup shader & camera + m_viewProj = camera.GetViewProjection(); + m_shader.Use(); + m_shader.SetMat4("u_ViewProj", m_viewProj); + m_shader.SetInt("u_Texture", 0); +} + +void Renderer::DrawSprite(const Sprite& s) { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, s.textureID); + + m_shader.SetVec2("u_Offset", s.position); + m_shader.SetVec2("u_Scale", s.size); + + glBindVertexArray(m_quadVAO); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr); +} + +void Renderer::EndScene() { + // unbind FBO → subsequent draws go to default framebuffer + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +void Renderer::CreateQuad() { + float verts[] = { + -0.5f, -0.5f, 0,0, + 0.5f, -0.5f, 1,0, + 0.5f, 0.5f, 1,1, + -0.5f, 0.5f, 0,1 + }; + unsigned int idx[] = {0,1,2, 2,3,0}; + + glGenVertexArrays(1, &m_quadVAO); + glGenBuffers(1, &m_quadVBO); + + glBindVertexArray(m_quadVAO); + glBindBuffer(GL_ARRAY_BUFFER, m_quadVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW); + + glEnableVertexAttribArray(0); + glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,4*sizeof(float),(void*)0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,4*sizeof(float),(void*)(2*sizeof(float))); + + glBindVertexArray(0); +} + +} // namespace OX diff --git a/src/core/renderer/Renderer.h b/src/core/renderer/Renderer.h index 01b3efc..37beb91 100644 --- a/src/core/renderer/Renderer.h +++ b/src/core/renderer/Renderer.h @@ -1,26 +1,73 @@ -// -// Created by spenc on 5/21/2025. -// +// File: src/Renderer.h +#pragma once -#ifndef RENDERER_H -#define RENDERER_H - -#include "glfw/glfw3.h" #include "glm/glm.hpp" +#include "systems/Shader.h" +#include namespace OX { -class Renderer { -public: - Renderer() = default; - ~Renderer() = default; + struct Sprite { + GLuint textureID; + glm::vec2 position; + glm::vec2 size; + }; - [[nodiscard]] GLuint GetRenderTarget() const {return m_renderTarget; } + class Camera2D { + public: + Camera2D(float left, float right, float bottom, float top); + void SetPosition(const glm::vec2& pos); + void SetZoom(float zoom); + const glm::mat4& GetViewProjection() const; -private: - GLuint m_renderTarget; -}; + private: + void Recalculate(); + glm::vec2 _position{0.0f}; + float _zoom{1.0f}; + float _left, _right, _bottom, _top; + glm::mat4 _viewProj{1.0f}; + }; -} // OX + class Renderer { + public: + Renderer() = default; + ~Renderer(); -#endif //RENDERER_H + /// Must call once after OpenGL context + GLEW init. + /// Provide the desired offscreen target size here. + void Init(int targetWidth, int targetHeight); + + /// Call at start of each scene (binds FBO and sets camera). + void BeginScene(const Camera2D& camera); + + /// Draw one sprite into the current scene. + void DrawSprite(const Sprite& sprite); + + /// Finish rendering, unbinds FBO. + void EndScene(); + + /// Access the color‐attachment texture ID that holds the rendered scene. + GLuint GetRenderTexture() const { return m_colorTex; } + + /// If window resized, call this to resize the offscreen target. + void ResizeTarget(int width, int height); + + private: + // offscreen + GLuint m_fbo = 0; + GLuint m_colorTex = 0; + GLuint m_depthRBO = 0; + + // quad geometry + GLuint m_quadVAO = 0, m_quadVBO = 0; + + // shader + Shader m_shader; + glm::mat4 m_viewProj{1.0f}; + + // helpers + void CreateQuad(); + void CreateFramebuffer(int width, int height); + }; + +} // namespace OX diff --git a/src/core/systems/Shader.cpp b/src/core/systems/Shader.cpp new file mode 100644 index 0000000..4caab21 --- /dev/null +++ b/src/core/systems/Shader.cpp @@ -0,0 +1,180 @@ +#include "Shader.h" +#include +#include +#include +#include "systems/Logger.h" + +namespace OX +{ + + Shader::Shader(const std::string &vertexPath, const std::string &fragmentPath) + { + LoadFromFiles(vertexPath, fragmentPath); + } + + bool Shader::LoadFromFiles(const std::string &vertexPath, const std::string &fragmentPath) + { + m_vertexPath = vertexPath; + m_fragmentPath = fragmentPath; + + std::string vertexSrc = ReadFile(vertexPath); + std::string fragmentSrc = ReadFile(fragmentPath); + + try { + m_vertTimestamp = std::filesystem::last_write_time(vertexPath); + m_fragTimestamp = std::filesystem::last_write_time(fragmentPath); + } catch (...) { + std::cerr << "[Shader] Warning: Could not get file timestamps.\n"; + } + + return LoadFromSource(vertexSrc, fragmentSrc); + } + + + bool Shader::LoadFromSource(const std::string &vertexSrc, const std::string &fragmentSrc) + { + GLuint vertexShader = CompileShader(GL_VERTEX_SHADER, vertexSrc); + GLuint fragmentShader = CompileShader(GL_FRAGMENT_SHADER, fragmentSrc); + + if (!vertexShader || !fragmentShader) return false; + + GLuint program = glCreateProgram(); + glAttachShader(program, vertexShader); + glAttachShader(program, fragmentShader); + glLinkProgram(program); + + GLint success; + glGetProgramiv(program, GL_LINK_STATUS, &success); + if (!success) { + char info[512]; + glGetProgramInfoLog(program, 512, nullptr, info); + std::cerr << "[Shader] Linking failed:\n" << info << '\n'; + glDeleteProgram(program); + return false; + } + + if (m_programID) + glDeleteProgram(m_programID); + + m_programID = program; + m_uniformCache.clear(); + + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + Logger::LogOk("Shader Loaded (%s,%s)", m_vertexPath.c_str(), m_fragmentPath.c_str()); + return true; + } + + void Shader::Reload() + { + OX_PROFILE_FUNCTION(); + LoadFromFiles(m_vertexPath, m_fragmentPath); + } + + void Shader::Use() const + { + glUseProgram(m_programID); + } + + GLuint Shader::CompileShader(GLenum type, const std::string &source) + { + OX_PROFILE_FUNCTION(); + GLuint shader = glCreateShader(type); + const char *src = source.c_str(); + glShaderSource(shader, 1, &src, nullptr); + glCompileShader(shader); + + GLint success; + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + if (!success) { + char info[512]; + glGetShaderInfoLog(shader, 512, nullptr, info); + + Logger::LogError("Failed to Compile Shader '%s', '%s'", m_fragmentPath.c_str(), info); + + return 0; + } + + return shader; + } + + void Shader::CheckHotReload() + { + OX_PROFILE_FUNCTION(); + + if (m_vertexPath.empty() || m_fragmentPath.empty()) { + return; + } + + try { + OX_PROFILE_LABEL("Get Write Time"); + auto vertTime = std::filesystem::last_write_time(m_vertexPath); + auto fragTime = std::filesystem::last_write_time(m_fragmentPath); + + if (vertTime != m_vertTimestamp || fragTime != m_fragTimestamp) { + std::cout << "[Shader] Reloading shader: " << m_vertexPath << " / " << m_fragmentPath << "\n"; + Reload(); + } + } catch (const std::exception &e) { + Logger::LogError("Failed to Reload Shader '%s', %s", m_vertexPath.c_str(), e.what()); + } + } + + + std::string Shader::ReadFile(const std::string &path) + { + if (!std::filesystem::exists(path)) { + std::ofstream file(path, std::ios::app); + } + std::ifstream file(path); + std::stringstream ss; + ss << file.rdbuf(); + return ss.str(); + } + + GLint Shader::GetUniformLocation(const std::string &name) + { + if (m_uniformCache.contains(name)) + return m_uniformCache[name]; + + GLint location = glGetUniformLocation(m_programID, name.c_str()); + m_uniformCache[name] = location; + return location; + } + + void Shader::SetInt(const std::string &name, int value) + { + glUniform1i(GetUniformLocation(name), value); + } + + void Shader::SetFloat(const std::string &name, float value) + { + glUniform1f(GetUniformLocation(name), value); + } + + void Shader::SetVec2(const std::string &name, const glm::vec2 &value) + { + glUniform2fv(GetUniformLocation(name), 1, &value[0]); + } + + void Shader::SetVec3(const std::string &name, const glm::vec3 &value) + { + glUniform3fv(GetUniformLocation(name), 1, &value[0]); + } + + void Shader::SetVec4(const std::string &name, const glm::vec4 &value) + { + glUniform4fv(GetUniformLocation(name), 1, &value[0]); + } + + void Shader::SetMat4(const std::string &name, const glm::mat4 &value) + { + glUniformMatrix4fv(GetUniformLocation(name), 1, GL_FALSE, &value[0][0]); + } + + + void Shader::SetBool(const std::string &name, bool value) + { + glUniform1i(GetUniformLocation(name), (int) value); + } +} diff --git a/src/core/systems/Shader.h b/src/core/systems/Shader.h new file mode 100644 index 0000000..9b26ba5 --- /dev/null +++ b/src/core/systems/Shader.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "systems/Profiler.h" + +namespace OX +{ + class Shader + { + public: + Shader() = default; + + Shader(const std::string &vertexPath, const std::string &fragmentPath); + + bool LoadFromFiles(const std::string &vertexPath, const std::string &fragmentPath); + + bool LoadFromSource(const std::string &vertexSrc, const std::string &fragmentSrc); + + void Reload(); + + void Use() const; + + void CheckHotReload(); // 🔥 + + GLuint GetID() const { return m_programID; } + + void SetInt(const std::string &name, int value); + + void SetFloat(const std::string &name, float value); + + void SetVec2(const std::string &name, const glm::vec2 &value); + + void SetVec3(const std::string &name, const glm::vec3 &value); + + void SetVec4(const std::string &name, const glm::vec4 &value); + + void SetMat4(const std::string &name, const glm::mat4 &value); + + void SetBool(const std::string &name, const bool value); + + private: + GLuint m_programID = 0; + std::string m_vertexPath; + std::string m_fragmentPath; + + std::unordered_map m_uniformCache; + + std::filesystem::file_time_type m_vertTimestamp; + std::filesystem::file_time_type m_fragTimestamp; + + GLuint CompileShader(GLenum type, const std::string &source); + + std::string ReadFile(const std::string &path); + + GLint GetUniformLocation(const std::string &name); + }; +} diff --git a/src/editor/Windows/Viewport.cpp b/src/editor/Windows/Viewport.cpp index ce269d9..53401bf 100644 --- a/src/editor/Windows/Viewport.cpp +++ b/src/editor/Windows/Viewport.cpp @@ -13,6 +13,8 @@ namespace OX { // Construct title std::string title = m_name + " (" + std::to_string(m_id) + ")"; + GLuint textureID = core.GetRenderer().GetRenderTexture(); // You must define this method + // Remove window padding ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); @@ -24,14 +26,17 @@ namespace OX ImVec2 viewportSize = ImGui::GetContentRegionAvail(); // === Fullscreen image === - GLuint textureID = core.GetRenderer().GetRenderTarget(); // You must define this method if (textureID != 0) { ImGui::Image(textureID, viewportSize, ImVec2(0, 1), ImVec2(1, 0)); } else { ImGui::Text("No Render Target."); } + ImGui::End(); ImGui::PopStyleVar(); // Restore padding + + core.GetRenderer().ResizeTarget(viewportSize.x, viewportSize.y); + } };