Compare commits

...

3 Commits

Author SHA1 Message Date
OusmBlueNinja
f7e0882597 Improves renderer and adds shader support
Adds shader class to core and modifies the renderer to use it,
while also adding profiling macros and fixing resize issues.
The changes ensure the render target is resized only when the
requested size is different from the current size and also clears
the screen to a dark gray color.
2025-05-21 22:05:10 -05:00
OusmBlueNinja
4194764d7a Started On Renderer 2025-05-21 21:55:54 -05:00
OusmBlueNinja
375af6829c Fixed Crash By Adding Lock 2025-05-21 21:31:47 -05:00
11 changed files with 539 additions and 35 deletions

View File

@ -99,6 +99,7 @@ add_library(Core STATIC
src/core/systems/Asset.h
src/core/systems/assets/Texture2D.cpp
src/core/systems/assets/Texture2D.h
src/core/systems/Shader.cpp
)
target_include_directories(Core PUBLIC src/core)

View File

@ -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<Texture2D>(asset);
renderer.DrawSprite({tex->GetID(), {100, 100}, {64, 64}});
}
renderer.EndScene();
for (auto &layer: m_layers) {
layer->Draw(*this);

View File

@ -1,9 +1,194 @@
//
// Created by spenc on 5/21/2025.
//
// File: src/Renderer.cpp
#include "Renderer.h"
#include <glm/gtc/matrix_transform.hpp>
#include <iostream>
namespace OX {
namespace OX
{
// ——— Camera2D implementation (unchanged) ———
Camera2D::Camera2D(float left, float right, float bottom, float top)
: _left(left), _right(right), _bottom(bottom), _top(top)
{
Recalculate();
}
} // OX
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)
{
size = {width, 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)
{
OX_PROFILE_FUNCTION();
if (size == Vec2i{width, height}) {
return;
}
CreateFramebuffer(width, height);
}
void Renderer::BeginScene(const Camera2D &camera)
{
OX_PROFILE_FUNCTION();
// bind offscreen
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
// clear
glViewport(0, 0, size.x, size.y);
glClearColor(0.1f, 0.1f, 0.1f, 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

View File

@ -1,26 +1,74 @@
//
// 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 <gl/glew.h>
#include "types/vec2.h"
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 colorattachment 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
Vec2i size;
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

View File

@ -18,6 +18,7 @@ namespace OX
// Map from actual file path to virtual ID
std::unordered_map<std::string, std::string> AssetManager::s_PathToID;
std::mutex AssetManager::s_AssetMutex;
std::mutex AssetManager::s_TreeMutex;
std::shared_ptr<ResourceTreeNode> AssetManager::s_FileTree = std::make_shared<ResourceTreeNode>();
@ -96,7 +97,7 @@ namespace OX
{
std::lock_guard<std::mutex> lock(s_AssetMutex);
// now fill in the real metadata
s_MetadataMap[resPath] = { type, path.string() };
s_MetadataMap[resPath] = {type, path.string()};
}
std::lock_guard<std::mutex> qlock(s_QueueMutex);
s_TextureQueue.push({resPath, w, h, ch, data});
@ -138,7 +139,6 @@ namespace OX
// Also map original file path to this ID
auto meta = s_MetadataMap[pending.id];
s_PathToID[meta.absolutePath] = pending.id;
}
}
@ -224,6 +224,7 @@ namespace OX
pos = next + 1;
}
if (pos < resPath.size()) parts.push_back(resPath.substr(pos));
std::lock_guard<std::mutex> lock(AssetManager::s_TreeMutex);
auto current = s_FileTree;
for (size_t i = 1; i < parts.size(); ++i) {

View File

@ -37,6 +37,9 @@ namespace OX {
static std::shared_ptr<ResourceTreeNode> GetFileTree();
static void SaveAssetPack(const std::string& outputPath);
static std::mutex s_TreeMutex;
private:
struct AssetMetadata {
std::string type;
@ -69,6 +72,6 @@ namespace OX {
// texture upload queue
static std::queue<PendingTexture> s_TextureQueue;
static std::mutex s_QueueMutex;
static std::mutex s_QueueMutex;
};
}

180
src/core/systems/Shader.cpp Normal file
View File

@ -0,0 +1,180 @@
#include "Shader.h"
#include <fstream>
#include <sstream>
#include <iostream>
#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);
}
}

61
src/core/systems/Shader.h Normal file
View File

@ -0,0 +1,61 @@
#pragma once
#include <string>
#include <unordered_map>
#include <glm/glm.hpp>
#include <GL/glew.h>
#include <filesystem>
#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<std::string, GLint> 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);
};
}

View File

@ -146,7 +146,7 @@ namespace OX
// --- Render ImGui onto FBO 0 ---
{
OX_PROFILE_LABEL("VSYNC Wait");
ImGui::EndFrame();
ImGui::Render();

View File

@ -1,6 +1,7 @@
// File: src/FileBrowser.cpp
#include "FileBrowser.h"
#include <filesystem>
#include "systems/Profiler.h"
namespace OX
{
@ -13,6 +14,7 @@ namespace OX
void FileBrowser::Draw(const char *title)
{
OX_PROFILE_FUNCTION();
ImGui::Begin(title);
// --- toolbar now contains back button, inline path, filter & view toggle all on one row ---
@ -86,6 +88,7 @@ namespace OX
// ——— Polished grid view ———
void FileBrowser::DrawGridView(const std::shared_ptr<ResourceTreeNode> &node)
{
OX_PROFILE_FUNCTION();
const float cellW = _cfg.thumbnailSize + _cfg.padding * 2;
ImVec2 avail = ImGui::GetContentRegionAvail();
int cols = std::max(1, int(avail.x / cellW));
@ -93,9 +96,15 @@ namespace OX
ImGui::BeginChild("GridRegion", ImVec2(0, 0), false, ImGuiWindowFlags_AlwaysUseWindowPadding);
ImGui::Columns(cols, nullptr, false);
for (auto &c: node->children) {
if (!_filter.empty() && !PassesFilter(c->name)) continue;
std::vector<std::shared_ptr<ResourceTreeNode> > children; {
std::lock_guard<std::mutex> lock(AssetManager::s_TreeMutex); // assume you add this mutex
children = node->children;
}
for (auto &c: children) {
if (!c) return;
if (!_filter.empty() && !PassesFilter(c->name)) continue;
ImGui::PushID(c->path.c_str());
ImGui::BeginGroup();

View File

@ -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);
}
};