From a02c300c0c6a9bccb323657e5c71d4c347c142b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Leclercq?= Date: Fri, 30 Oct 2020 23:08:55 +0100 Subject: [PATCH] Add GraphicsTest demo --- examples/GraphicsTest/build.lua | 16 ++ examples/GraphicsTest/main.cpp | 395 ++++++++++++++++++++++++++++++++ 2 files changed, 411 insertions(+) create mode 100644 examples/GraphicsTest/build.lua create mode 100644 examples/GraphicsTest/main.cpp diff --git a/examples/GraphicsTest/build.lua b/examples/GraphicsTest/build.lua new file mode 100644 index 000000000..170d65770 --- /dev/null +++ b/examples/GraphicsTest/build.lua @@ -0,0 +1,16 @@ +EXAMPLE.Name = "GraphicsTest" + +EXAMPLE.EnableConsole = true + +EXAMPLE.Files = { + "main.cpp" +} + +EXAMPLE.Libraries = { + "NazaraCore", + "NazaraGraphics", + "NazaraPlatform", + "NazaraRenderer", + "NazaraShader", + "NazaraUtility" +} diff --git a/examples/GraphicsTest/main.cpp b/examples/GraphicsTest/main.cpp new file mode 100644 index 000000000..23f7ca17d --- /dev/null +++ b/examples/GraphicsTest/main.cpp @@ -0,0 +1,395 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main() +{ + Nz::Renderer::Config rendererConfig; + std::cout << "Run using Vulkan? (y/n)" << std::endl; + if (std::getchar() == 'y') + rendererConfig.preferredAPI = Nz::RenderAPI::Vulkan; + else + rendererConfig.preferredAPI = Nz::RenderAPI::OpenGL; + + Nz::Modules nazara(rendererConfig); + + Nz::RenderWindow window; + + Nz::MeshParams meshParams; + meshParams.matrix = Nz::Matrix4f::Rotate(Nz::EulerAnglesf(0.f, 90.f, 180.f)) * Nz::Matrix4f::Scale(Nz::Vector3f(0.002f)); + meshParams.vertexDeclaration = Nz::VertexDeclaration::Get(Nz::VertexLayout_XYZ_Normal_UV); + + std::string windowTitle = "Graphics Test"; + if (!window.Create(Nz::VideoMode(800, 600, 32), windowTitle)) + { + std::cout << "Failed to create Window" << std::endl; + return __LINE__; + } + + std::shared_ptr device = window.GetRenderDevice(); + + /*auto fragmentShader = device->InstantiateShaderStage(Nz::ShaderStageType::Fragment, Nz::ShaderLanguage::NazaraBinary, "frag.shader"); + if (!fragmentShader) + { + std::cout << "Failed to instantiate fragment shader" << std::endl; + return __LINE__; + } + + auto vertexShader = device->InstantiateShaderStage(Nz::ShaderStageType::Vertex, Nz::ShaderLanguage::NazaraBinary, "vert.shader"); + if (!vertexShader) + { + std::cout << "Failed to instantiate fragment shader" << std::endl; + return __LINE__; + }*/ + + Nz::MeshRef drfreak = Nz::Mesh::LoadFromFile("resources/Spaceship/spaceship.obj", meshParams); + if (!drfreak) + { + NazaraError("Failed to load model"); + return __LINE__; + } + + Nz::StaticMesh* drfreakMesh = static_cast(drfreak->GetSubMesh(0)); + + const Nz::VertexBuffer* drfreakVB = drfreakMesh->GetVertexBuffer(); + const Nz::IndexBuffer* drfreakIB = drfreakMesh->GetIndexBuffer(); + + // Index buffer + std::cout << "Index count: " << drfreakIB->GetIndexCount() << std::endl; + + // Vertex buffer + std::cout << "Vertex count: " << drfreakVB->GetVertexCount() << std::endl; + + // Texture + Nz::ImageRef drfreakImage = Nz::Image::LoadFromFile("resources/Spaceship/Texture/diffuse.png"); + if (!drfreakImage || !drfreakImage->Convert(Nz::PixelFormat_RGBA8)) + { + NazaraError("Failed to load image"); + return __LINE__; + } + + Nz::TextureInfo texParams; + texParams.pixelFormat = drfreakImage->GetFormat(); + texParams.type = drfreakImage->GetType(); + texParams.width = drfreakImage->GetWidth(); + texParams.height = drfreakImage->GetHeight(); + texParams.depth = drfreakImage->GetDepth(); + + std::shared_ptr texture = device->InstantiateTexture(texParams); + if (!texture->Update(drfreakImage->GetConstPixels())) + { + NazaraError("Failed to update texture"); + return __LINE__; + } + + std::shared_ptr textureSampler = device->InstantiateTextureSampler({}); + + Nz::Material material(Nz::BasicMaterial::GetSettings()); + //material.SetShader(Nz::ShaderStageType::Fragment, fragmentShader); + //material.SetShader(Nz::ShaderStageType::Vertex, vertexShader); + material.SetTexture(0, texture); + material.SetTextureSampler(0, textureSampler); + + Nz::PredefinedInstanceData instanceUboOffsets = Nz::PredefinedInstanceData::GetOffsets(); + Nz::PredefinedViewerData viewerUboOffsets = Nz::PredefinedViewerData::GetOffsets(); + + std::vector instanceDataBuffer(instanceUboOffsets.totalSize); + std::vector viewerDataBuffer(viewerUboOffsets.totalSize); + + Nz::Vector2ui windowSize = window.GetSize(); + + Nz::AccessByOffset(instanceDataBuffer.data(), instanceUboOffsets.worldMatrixOffset) = Nz::Matrix4f::Translate(Nz::Vector3f::Forward() * 2 + Nz::Vector3f::Right()); + Nz::AccessByOffset(viewerDataBuffer.data(), viewerUboOffsets.viewMatrixOffset) = Nz::Matrix4f::Translate(Nz::Vector3f::Backward() * 1); + Nz::AccessByOffset(viewerDataBuffer.data(), viewerUboOffsets.projMatrixOffset) = Nz::Matrix4f::Perspective(70.f, float(windowSize.x) / windowSize.y, 0.1f, 1000.f); + + Nz::RenderPipelineLayoutInfo pipelineLayoutInfo; + { + auto& uboBinding = pipelineLayoutInfo.bindings.emplace_back(); + uboBinding.index = 0; + uboBinding.shaderStageFlags = Nz::ShaderStageType::Vertex; + uboBinding.type = Nz::ShaderBindingType::UniformBuffer; + } + + { + auto& uboBinding = pipelineLayoutInfo.bindings.emplace_back(); + uboBinding.index = 1; + uboBinding.shaderStageFlags = Nz::ShaderStageType::Vertex; + uboBinding.type = Nz::ShaderBindingType::UniformBuffer; + } + + { + auto& textureBinding = pipelineLayoutInfo.bindings.emplace_back(); + textureBinding.index = 2; + textureBinding.shaderStageFlags = Nz::ShaderStageType::Fragment; + textureBinding.type = Nz::ShaderBindingType::Texture; + } + + //std::shared_ptr renderPipelineLayout = device->InstantiateRenderPipelineLayout(std::move(pipelineLayoutInfo)); + std::shared_ptr renderPipelineLayout = material.GetSettings()->GetRenderPipelineLayout(); + + Nz::ShaderBindingPtr shaderBinding = renderPipelineLayout->AllocateShaderBinding(); + + std::shared_ptr instanceDataUbo = device->InstantiateBuffer(Nz::BufferType_Uniform); + if (!instanceDataUbo->Initialize(instanceDataBuffer.size(), Nz::BufferUsage_DeviceLocal | Nz::BufferUsage_Dynamic)) + { + NazaraError("Failed to create instance UBO"); + return __LINE__; + } + + instanceDataUbo->Fill(instanceDataBuffer.data(), 0, instanceDataBuffer.size()); + + std::shared_ptr viewerDataUbo = device->InstantiateBuffer(Nz::BufferType_Uniform); + if (!viewerDataUbo->Initialize(viewerDataBuffer.size(), Nz::BufferUsage_DeviceLocal | Nz::BufferUsage_Dynamic)) + { + NazaraError("Failed to create viewer UBO"); + return __LINE__; + } + + shaderBinding->Update({ + { + 5, + Nz::ShaderBinding::UniformBufferBinding { + viewerDataUbo.get(), 0, viewerDataBuffer.size() + } + }, + { + 4, + Nz::ShaderBinding::UniformBufferBinding { + instanceDataUbo.get(), 0, instanceDataBuffer.size() + } + }, + { + 1, + Nz::ShaderBinding::TextureBinding { + texture.get(), textureSampler.get() + } + } + }); + + Nz::RenderPipelineInfo pipelineInfo; + pipelineInfo.pipelineLayout = renderPipelineLayout; + + pipelineInfo.depthBuffer = true; + pipelineInfo.shaderStages.emplace_back(material.GetShader(Nz::ShaderStageType::Fragment)); + pipelineInfo.shaderStages.emplace_back(material.GetShader(Nz::ShaderStageType::Vertex)); + + auto& vertexBuffer = pipelineInfo.vertexBuffers.emplace_back(); + vertexBuffer.binding = 0; + vertexBuffer.declaration = drfreakVB->GetVertexDeclaration(); + + //std::shared_ptr pipeline = material.GetPipeline()->GetRenderPipeline(pipelineInfo.vertexBuffers); + + std::shared_ptr pipeline = device->InstantiateRenderPipeline(pipelineInfo); + + Nz::RenderDevice* renderDevice = window.GetRenderDevice().get(); + + Nz::RenderWindowImpl* windowImpl = window.GetImpl(); + std::shared_ptr commandPool = windowImpl->CreateCommandPool(Nz::QueueType::Graphics); + + Nz::RenderBuffer* renderBufferIB = static_cast(drfreakIB->GetBuffer()->GetImpl()); + Nz::RenderBuffer* renderBufferVB = static_cast(drfreakVB->GetBuffer()->GetImpl()); + + if (!renderBufferIB->Synchronize(renderDevice)) + { + NazaraError("Failed to synchronize render buffer"); + return __LINE__; + } + + if (!renderBufferVB->Synchronize(renderDevice)) + { + NazaraError("Failed to synchronize render buffer"); + return __LINE__; + } + + Nz::AbstractBuffer* indexBufferImpl = renderBufferIB->GetHardwareBuffer(renderDevice); + Nz::AbstractBuffer* vertexBufferImpl = renderBufferVB->GetHardwareBuffer(renderDevice); + + Nz::CommandBufferPtr drawCommandBuffer; + auto RebuildCommandBuffer = [&] + { + Nz::Vector2ui windowSize = window.GetSize(); + + drawCommandBuffer = commandPool->BuildCommandBuffer([&](Nz::CommandBufferBuilder& builder) + { + Nz::Recti renderRect(0, 0, window.GetSize().x, window.GetSize().y); + + Nz::CommandBufferBuilder::ClearValues clearValues[2]; + clearValues[0].color = Nz::Color::Black; + clearValues[1].depth = 1.f; + clearValues[1].stencil = 0; + + builder.BeginDebugRegion("Main window rendering", Nz::Color::Green); + { + builder.BeginRenderPass(windowImpl->GetFramebuffer(), windowImpl->GetRenderPass(), renderRect, { clearValues[0], clearValues[1] }); + { + builder.BindIndexBuffer(indexBufferImpl); + builder.BindPipeline(*pipeline); + builder.BindVertexBuffer(0, vertexBufferImpl); + builder.BindShaderBinding(*shaderBinding); + + builder.SetScissor(Nz::Recti{ 0, 0, int(windowSize.x), int(windowSize.y) }); + builder.SetViewport(Nz::Recti{ 0, 0, int(windowSize.x), int(windowSize.y) }); + + builder.DrawIndexed(drfreakIB->GetIndexCount()); + } + builder.EndRenderPass(); + } + builder.EndDebugRegion(); + }); + }; + RebuildCommandBuffer(); + + + Nz::Vector3f viewerPos = Nz::Vector3f::Zero(); + + Nz::EulerAnglesf camAngles(0.f, 0.f, 0.f); + Nz::Quaternionf camQuat(camAngles); + + window.EnableEventPolling(true); + + Nz::Clock updateClock; + Nz::Clock secondClock; + unsigned int fps = 0; + bool viewerUboUpdate = true; + + Nz::Mouse::SetRelativeMouseMode(true); + + while (window.IsOpen()) + { + Nz::WindowEvent event; + while (window.PollEvent(&event)) + { + switch (event.type) + { + case Nz::WindowEventType_Quit: + window.Close(); + break; + + case Nz::WindowEventType_MouseMoved: // La souris a bougé + { + // Gestion de la caméra free-fly (Rotation) + float sensitivity = 0.3f; // Sensibilité de la souris + + // On modifie l'angle de la caméra grâce au déplacement relatif sur X de la souris + camAngles.yaw = Nz::NormalizeAngle(camAngles.yaw - event.mouseMove.deltaX*sensitivity); + + // Idem, mais pour éviter les problèmes de calcul de la matrice de vue, on restreint les angles + camAngles.pitch = Nz::Clamp(camAngles.pitch + event.mouseMove.deltaY*sensitivity, -89.f, 89.f); + + camQuat = camAngles; + + viewerUboUpdate = true; + break; + } + + case Nz::WindowEventType_Resized: + { + Nz::Vector2ui windowSize = window.GetSize(); + Nz::AccessByOffset(viewerDataBuffer.data(), viewerUboOffsets.projMatrixOffset) = Nz::Matrix4f::Perspective(70.f, float(windowSize.x) / windowSize.y, 0.1f, 1000.f); + viewerUboUpdate = true; + break; + } + + default: + break; + } + } + + if (updateClock.GetMilliseconds() > 1000 / 60) + { + float cameraSpeed = 2.f * updateClock.GetSeconds(); + updateClock.Restart(); + + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Up) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Z)) + viewerPos += camQuat * Nz::Vector3f::Forward() * cameraSpeed; + + // Si la flèche du bas ou la touche S est pressée, on recule + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Down) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::S)) + viewerPos += camQuat * Nz::Vector3f::Backward() * cameraSpeed; + + // Etc... + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Left) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Q)) + viewerPos += camQuat * Nz::Vector3f::Left() * cameraSpeed; + + // Etc... + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Right) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::D)) + viewerPos += camQuat * Nz::Vector3f::Right() * cameraSpeed; + + // Majuscule pour monter, notez l'utilisation d'une direction globale (Non-affectée par la rotation) + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::LShift) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::RShift)) + viewerPos += Nz::Vector3f::Up() * cameraSpeed; + + // Contrôle (Gauche ou droite) pour descendre dans l'espace global, etc... + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::LControl) || Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::RControl)) + viewerPos += Nz::Vector3f::Down() * cameraSpeed; + + viewerUboUpdate = true; + } + + Nz::RenderFrame frame = windowImpl->Acquire(); + if (!frame) + continue; + + if (frame.IsFramebufferInvalidated()) + RebuildCommandBuffer(); + + Nz::AccessByOffset(viewerDataBuffer.data(), viewerUboOffsets.viewMatrixOffset) = Nz::Matrix4f::ViewMatrix(viewerPos, camAngles); + + if (viewerUboUpdate) + { + auto& allocation = frame.GetUploadPool().Allocate(viewerDataBuffer.size()); + + std::memcpy(allocation.mappedPtr, viewerDataBuffer.data(), viewerDataBuffer.size()); + + frame.Execute([&](Nz::CommandBufferBuilder& builder) + { + builder.BeginDebugRegion("UBO Update", Nz::Color::Yellow); + { + builder.PreTransferBarrier(); + builder.CopyBuffer(allocation, viewerDataUbo.get()); + builder.PostTransferBarrier(); + } + builder.EndDebugRegion(); + }, Nz::QueueType::Transfer); + + viewerUboUpdate = false; + } + + frame.SubmitCommandBuffer(drawCommandBuffer.get(), Nz::QueueType::Graphics); + + frame.Present(); + + window.Display(); + + // On incrémente le compteur de FPS improvisé + fps++; + + if (secondClock.GetMilliseconds() >= 1000) // Toutes les secondes + { + // Et on insère ces données dans le titre de la fenêtre + window.SetTitle(windowTitle + " - " + Nz::NumberToString(fps) + " FPS"); + + /* + Note: En C++11 il est possible d'insérer de l'Unicode de façon standard, quel que soit l'encodage du fichier, + via quelque chose de similaire à u8"Cha\u00CEne de caract\u00E8res". + Cependant, si le code source est encodé en UTF-8 (Comme c'est le cas dans ce fichier), + cela fonctionnera aussi comme ceci : "Chaîne de caractères". + */ + + // Et on réinitialise le compteur de FPS + fps = 0; + + // Et on relance l'horloge pour refaire ça dans une seconde + secondClock.Restart(); + } + } + + return EXIT_SUCCESS; +}