diff --git a/tests/RHI/OpenGL/integration/CMakeLists.txt b/tests/RHI/OpenGL/integration/CMakeLists.txt index d46d3308..ca962259 100644 --- a/tests/RHI/OpenGL/integration/CMakeLists.txt +++ b/tests/RHI/OpenGL/integration/CMakeLists.txt @@ -7,3 +7,4 @@ find_package(Python3 REQUIRED) enable_testing() add_subdirectory(minimal) +add_subdirectory(triangle) diff --git a/tests/RHI/OpenGL/integration/triangle/CMakeLists.txt b/tests/RHI/OpenGL/integration/triangle/CMakeLists.txt new file mode 100644 index 00000000..123fae74 --- /dev/null +++ b/tests/RHI/OpenGL/integration/triangle/CMakeLists.txt @@ -0,0 +1,57 @@ +cmake_minimum_required(VERSION 3.15) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + +project(OpenGL_Triangle) + +set(ENGINE_ROOT_DIR ${CMAKE_SOURCE_DIR}/engine) +set(PACKAGE_DIR ${CMAKE_SOURCE_DIR}/tests/OpenGL/package) + +add_executable(OpenGL_Triangle + WIN32 + main.cpp + ${PACKAGE_DIR}/src/glad.c +) + +target_include_directories(OpenGL_Triangle PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${ENGINE_ROOT_DIR}/include + ${PACKAGE_DIR}/include +) + +target_link_libraries(OpenGL_Triangle PRIVATE + opengl32 + XCEngine +) + +target_compile_definitions(OpenGL_Triangle PRIVATE + UNICODE + _UNICODE +) + +add_custom_command(TARGET OpenGL_Triangle POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/Res + $/Res +) + +add_custom_command(TARGET OpenGL_Triangle POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_SOURCE_DIR}/tests/RHI/OpenGL/integration/run_integration_test.py + $/ + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_SOURCE_DIR}/tests/RHI/OpenGL/integration/compare_ppm.py + $/ + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/GT.ppm + $/ +) + +add_test(NAME OpenGL_Triangle_Integration + COMMAND ${Python3_EXECUTABLE} $/run_integration_test.py + $ + triangle.ppm + ${CMAKE_CURRENT_SOURCE_DIR}/GT.ppm + 5 + WORKING_DIRECTORY $ +) \ No newline at end of file diff --git a/tests/RHI/OpenGL/integration/triangle/GT.ppm b/tests/RHI/OpenGL/integration/triangle/GT.ppm new file mode 100644 index 00000000..6fd275e4 Binary files /dev/null and b/tests/RHI/OpenGL/integration/triangle/GT.ppm differ diff --git a/tests/RHI/OpenGL/integration/triangle/Res/Shader/triangle.frag b/tests/RHI/OpenGL/integration/triangle/Res/Shader/triangle.frag new file mode 100644 index 00000000..96cfeb02 --- /dev/null +++ b/tests/RHI/OpenGL/integration/triangle/Res/Shader/triangle.frag @@ -0,0 +1,8 @@ +#version 460 + +in vec4 vColor; +out vec4 fragColor; + +void main() { + fragColor = vColor; +} \ No newline at end of file diff --git a/tests/RHI/OpenGL/integration/triangle/Res/Shader/triangle.vert b/tests/RHI/OpenGL/integration/triangle/Res/Shader/triangle.vert new file mode 100644 index 00000000..c51443ea --- /dev/null +++ b/tests/RHI/OpenGL/integration/triangle/Res/Shader/triangle.vert @@ -0,0 +1,11 @@ +#version 460 + +layout(location = 0) in vec4 aPosition; +layout(location = 1) in vec4 aColor; + +out vec4 vColor; + +void main() { + gl_Position = aPosition; + vColor = aColor; +} \ No newline at end of file diff --git a/tests/RHI/OpenGL/integration/triangle/main.cpp b/tests/RHI/OpenGL/integration/triangle/main.cpp new file mode 100644 index 00000000..04e6e554 --- /dev/null +++ b/tests/RHI/OpenGL/integration/triangle/main.cpp @@ -0,0 +1,203 @@ +#include +#include +#include +#include + +#include + +#include + +#include "XCEngine/RHI/OpenGL/OpenGLDevice.h" +#include "XCEngine/RHI/OpenGL/OpenGLSwapChain.h" +#include "XCEngine/RHI/OpenGL/OpenGLCommandList.h" +#include "XCEngine/RHI/OpenGL/OpenGLBuffer.h" +#include "XCEngine/RHI/OpenGL/OpenGLVertexArray.h" +#include "XCEngine/RHI/OpenGL/OpenGLShader.h" +#include "XCEngine/RHI/OpenGL/OpenGLPipelineState.h" +#include "XCEngine/RHI/OpenGL/OpenGLScreenshot.h" +#include "XCEngine/Debug/Logger.h" +#include "XCEngine/Debug/ConsoleLogSink.h" +#include "XCEngine/Containers/String.h" + +#pragma comment(lib, "opengl32.lib") + +using namespace XCEngine::RHI; +using namespace XCEngine::Debug; +using namespace XCEngine::Containers; + +static const int gWidth = 1280; +static const int gHeight = 720; + +void Log(const char* format, ...) { + char buffer[1024]; + va_list args; + va_start(args, format); + vsnprintf(buffer, sizeof(buffer), format, args); + va_end(args); + Logger::Get().Debug(LogCategory::Rendering, String(buffer)); +} + +LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + switch (msg) { + case WM_CLOSE: + PostQuitMessage(0); + break; + } + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { + Logger::Get().Initialize(); + Logger::Get().AddSink(std::make_unique()); + Logger::Get().SetMinimumLevel(LogLevel::Debug); + + Log("[INFO] OpenGL Triangle Test Starting"); + + WNDCLASSEXW wc = {}; + wc.cbSize = sizeof(WNDCLASSEXW); + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = WindowProc; + wc.hInstance = hInstance; + wc.lpszClassName = L"XCEngine_OpenGL_Triangle"; + + if (!RegisterClassExW(&wc)) { + Log("[ERROR] Failed to register window class"); + return -1; + } + + RECT rect = { 0, 0, gWidth, gHeight }; + AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE); + + HWND hwnd = CreateWindowExW( + 0, + L"XCEngine_OpenGL_Triangle", + L"OpenGL Triangle Test", + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, + rect.right - rect.left, rect.bottom - rect.top, + NULL, NULL, hInstance, NULL + ); + + if (!hwnd) { + Log("[ERROR] Failed to create window"); + return -1; + } + + OpenGLDevice device; + if (!device.InitializeWithExistingWindow(hwnd)) { + Log("[ERROR] Failed to initialize OpenGL device"); + return -1; + } + + ShowWindow(hwnd, nShowCmd); + UpdateWindow(hwnd); + + Log("[INFO] OpenGL Device: %S", device.GetDeviceInfo().renderer.c_str()); + Log("[INFO] OpenGL Version: %S", device.GetDeviceInfo().version.c_str()); + + OpenGLSwapChain swapChain; + swapChain.Initialize(hwnd, gWidth, gHeight); + + OpenGLCommandList commandList; + + struct Vertex { + float pos[4]; + float col[4]; + }; + + Vertex vertices[] = { + { { 0.0f, 0.5f, 0.0f, 1.0f }, { 1.0f, 0.0f, 0.0f, 1.0f } }, + { { -0.5f, -0.5f, 0.0f, 1.0f }, { 0.0f, 1.0f, 0.0f, 1.0f } }, + { { 0.5f, -0.5f, 0.0f, 1.0f }, { 0.0f, 0.0f, 1.0f, 1.0f } }, + }; + + OpenGLBuffer vertexBuffer; + if (!vertexBuffer.InitializeVertexBuffer(vertices, sizeof(vertices))) { + Log("[ERROR] Failed to initialize vertex buffer"); + return -1; + } + vertexBuffer.SetStride(sizeof(Vertex)); + + OpenGLVertexArray vertexArray; + vertexArray.Initialize(); + + VertexAttribute posAttr = {}; + posAttr.index = 0; + posAttr.count = 4; + posAttr.type = GL_FLOAT; + posAttr.normalized = GL_FALSE; + posAttr.stride = sizeof(Vertex); + posAttr.offset = 0; + vertexArray.AddVertexBuffer(vertexBuffer.GetID(), posAttr); + + VertexAttribute colAttr = {}; + colAttr.index = 1; + colAttr.count = 4; + colAttr.type = GL_FLOAT; + colAttr.normalized = GL_FALSE; + colAttr.stride = sizeof(Vertex); + colAttr.offset = sizeof(float) * 4; + vertexArray.AddVertexBuffer(vertexBuffer.GetID(), colAttr); + + OpenGLShader shader; + if (!shader.CompileFromFile("Res/Shader/triangle.vert", "Res/Shader/triangle.frag")) { + Log("[ERROR] Failed to compile shaders"); + return -1; + } + Log("[INFO] Shaders compiled successfully"); + + OpenGLPipelineState pipelineState; + OpenGLRasterizerState rasterizerState; + rasterizerState.cullFaceEnable = false; + pipelineState.SetRasterizerState(rasterizerState); + + OpenGLDepthStencilState depthStencilState; + depthStencilState.depthTestEnable = false; + depthStencilState.depthWriteEnable = false; + pipelineState.SetDepthStencilState(depthStencilState); + + ViewportState viewportState = { 0.0f, 0.0f, (float)gWidth, (float)gHeight, 0.0f, 1.0f }; + pipelineState.SetViewport(viewportState); + + pipelineState.AttachShader(shader.GetID()); + pipelineState.Apply(); + + MSG msg = {}; + int frameCount = 0; + const int targetFrameCount = 30; + + while (frameCount < targetFrameCount) { + if (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) { + break; + } + TranslateMessage(&msg); + DispatchMessageW(&msg); + } else { + commandList.SetViewport(0, 0, gWidth, gHeight); + commandList.Clear(0.0f, 0.0f, 1.0f, 1.0f, 1); + + pipelineState.Bind(); + vertexArray.Bind(); + commandList.Draw(PrimitiveType::Triangles, 3, 0); + + swapChain.Present(0, 0); + frameCount++; + } + } + + Log("[INFO] Reached target frame count %d - taking screenshot!", targetFrameCount); + OpenGLScreenshot::Capture(device, swapChain, "triangle.ppm"); + + vertexArray.Shutdown(); + vertexBuffer.Shutdown(); + shader.Shutdown(); + pipelineState.Shutdown(); + + swapChain.Shutdown(); + device.Shutdown(); + Logger::Get().Shutdown(); + + Log("[INFO] OpenGL Triangle Test Finished"); + return 0; +} \ No newline at end of file