diff --git a/assets/shaders/vulkan-test.frag b/assets/shaders/vulkan-test.frag new file mode 100644 index 0000000..7c5b0e7 --- /dev/null +++ b/assets/shaders/vulkan-test.frag @@ -0,0 +1,9 @@ +#version 450 + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} diff --git a/assets/shaders/vulkan-test.frag.spv b/assets/shaders/vulkan-test.frag.spv new file mode 100644 index 0000000..da37f7e Binary files /dev/null and b/assets/shaders/vulkan-test.frag.spv differ diff --git a/assets/shaders/vulkan-test.vert b/assets/shaders/vulkan-test.vert new file mode 100644 index 0000000..02d6810 --- /dev/null +++ b/assets/shaders/vulkan-test.vert @@ -0,0 +1,20 @@ +#version 450 + +layout(location = 0) out vec3 fragColor; + +vec2 positions[3] = vec2[]( +vec2(0.0, -0.5), +vec2(0.5, 0.5), +vec2(-0.5, 0.5) +); + +vec3 colors[3] = vec3[]( +vec3(1.0, 0.0, 0.0), +vec3(0.0, 1.0, 0.0), +vec3(0.0, 0.0, 1.0) +); + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + fragColor = colors[gl_VertexIndex]; +} diff --git a/assets/shaders/vulkan-test.vert.spv b/assets/shaders/vulkan-test.vert.spv new file mode 100644 index 0000000..cf7123f Binary files /dev/null and b/assets/shaders/vulkan-test.vert.spv differ diff --git a/engine/src/VulkanTest.cpp b/engine/src/VulkanTest.cpp new file mode 100644 index 0000000..7f21928 --- /dev/null +++ b/engine/src/VulkanTest.cpp @@ -0,0 +1,700 @@ +#include "pch/enginepch.h" +#include "VulkanTest.h" + +#include + +int mainVulkan() { + VulkanTest app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +void VulkanTest::run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); +} + +void VulkanTest::initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(1920, 1080, "Vulkan", nullptr, nullptr); +} + +void VulkanTest::initVulkan() { + createInstance(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createCommandBuffers(); + createSyncObjects(); +} + +void VulkanTest::cleanup() const { + cleanupSwapChain(); + device.destroyPipeline(graphicsPipeline); + device.destroyPipelineLayout(pipelineLayout); + device.destroyRenderPass(renderPass); + for (size_t i = 0; i < maxFramesInFlight; i++) { + device.destroySemaphore(renderFinishedSemaphores[i]); + device.destroySemaphore(imageAvailableSemaphores[i]); + device.destroyFence(inFlightFences[i]); + } + device.destroyCommandPool(commandPool); + device.destroy(); + instance.destroySurfaceKHR(surface); + instance.destroy(); + glfwDestroyWindow(window); + glfwTerminate(); +} + +void VulkanTest::mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + device.waitIdle(); +} + +void VulkanTest::drawFrame() { + const vk::Semaphore imageAvailableSemaphore = imageAvailableSemaphores[currentFrame]; + const vk::Semaphore renderFinishedSemaphore = renderFinishedSemaphores[currentFrame]; + const vk::Fence inFlightFence = inFlightFences[currentFrame]; + const vk::CommandBuffer commandBuffer = commandBuffers[currentFrame]; + + if (device.waitForFences(1, &inFlightFence, true, std::numeric_limits::max()) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to wait for fence"); + } + + const auto acquireResult = device.acquireNextImageKHR(swapChain, std::numeric_limits::max(), imageAvailableSemaphore, nullptr); + if (acquireResult.result == vk::Result::eErrorOutOfDateKHR) { + recreateSwapChain(); + return; + } + if (acquireResult.result != vk::Result::eSuccess && acquireResult.result != vk::Result::eSuboptimalKHR) { + throw std::runtime_error("Failed to acquire swap chain image"); + } + + if (device.resetFences(1, &inFlightFence) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to reset fence"); + } + + commandBuffer.reset(); + recordCommandBuffer(commandBuffer, acquireResult.value); + + vk::SubmitInfo submitInfo{}; + vk::Semaphore waitSemaphores[] = {imageAvailableSemaphore}; + vk::PipelineStageFlags waitStages[] = {vk::PipelineStageFlagBits::eColorAttachmentOutput}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + vk::Semaphore signalSemaphores[] = {renderFinishedSemaphore}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + if (graphicsQueue.submit(1, &submitInfo, inFlightFence) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to submit draw command buffer"); + } + + vk::PresentInfoKHR presentInfo{}; + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + vk::SwapchainKHR swapChains[] = {swapChain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &acquireResult.value; + const auto presentResult = presentQueue.presentKHR(&presentInfo); + if (presentResult == vk::Result::eErrorOutOfDateKHR || presentResult == vk::Result::eSuboptimalKHR) { + recreateSwapChain(); + } else if (presentResult != vk::Result::eSuccess) { + throw std::runtime_error("Failed to present swap chain image"); + } + + currentFrame = (currentFrame + 1) % maxFramesInFlight; +} + +void VulkanTest::createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available"); + } + + vk::ApplicationInfo appInfo{}; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + vk::InstanceCreateInfo createInfo{}; + createInfo.pApplicationInfo = &appInfo; + + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + createInfo.enabledExtensionCount = glfwExtensionCount; + createInfo.ppEnabledExtensionNames = glfwExtensions; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vk::createInstance(&createInfo, nullptr, &instance) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to create instance"); + } +} + +bool VulkanTest::checkValidationLayerSupport() const { +#if defined(VK_ADD_LAYER_PATH) && defined(DE_PLATFORM_WINDOWS) + _putenv_s("VK_ADD_LAYER_PATH", VK_ADD_LAYER_PATH); +#endif + + uint32_t layerCount; + if (vk::enumerateInstanceLayerProperties(&layerCount, nullptr) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to enumerate instance layer properties"); + } + + std::vector availableLayers(layerCount); + if (vk::enumerateInstanceLayerProperties(&layerCount, availableLayers.data()) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to enumerate instance layer properties"); + } + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; +} + +void VulkanTest::pickPhysicalDevice() { + uint32_t deviceCount = 0; + if (instance.enumeratePhysicalDevices(&deviceCount, nullptr) != vk::Result::eSuccess || deviceCount == 0) { + throw std::runtime_error("Failed to find GPUs with Vulkan support"); + } + + std::vector devices(deviceCount); + if (instance.enumeratePhysicalDevices(&deviceCount, devices.data()) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to enumerate physical devices"); + } + + std::multimap candidates; + for (const auto& device : devices) { + unsigned int score = rateDeviceSuitability(device); + candidates.insert(std::make_pair(score, device)); + } + + if (candidates.rbegin()->first > 0) { + physicalDevice = candidates.rbegin()->second; + std::cout << "Selected GPU: " << physicalDevice.getProperties().deviceName << std::endl; + } else { + throw std::runtime_error("Failed to find a suitable GPU"); + } +} + +void VulkanTest::createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (unsigned int queueFamily : uniqueQueueFamilies) { + vk::DeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.emplace_back(queueCreateInfo); + } + + vk::PhysicalDeviceFeatures deviceFeatures{}; + vk::DeviceCreateInfo createInfo{}; + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pEnabledFeatures = &deviceFeatures; + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + if (physicalDevice.createDevice(&createInfo, nullptr, &device) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to create logical device"); + } + + graphicsQueue = device.getQueue(indices.graphicsFamily.value(), 0); + presentQueue = device.getQueue(indices.presentFamily.value(), 0); +} + +void VulkanTest::createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, reinterpret_cast(&surface)) != VK_SUCCESS) { + throw std::runtime_error("Failed to create window surface"); + } +} + +void VulkanTest::createSwapChain() { + auto [capabilities, formats, presentModes] = querySwapChainSupport(physicalDevice); + vk::SurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(formats); + vk::PresentModeKHR presentMode = chooseSwapPresentMode(presentModes); + vk::Extent2D extent = chooseSwapExtent(capabilities); + unsigned int imageCount = capabilities.minImageCount + 1; + if (capabilities.maxImageCount > 0 && imageCount > capabilities.maxImageCount) { + imageCount = capabilities.maxImageCount; + } + vk::SwapchainCreateInfoKHR createInfo{}; + createInfo.surface = surface; + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = vk::ImageUsageFlagBits::eColorAttachment; + const QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + const unsigned int queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = vk::SharingMode::eConcurrent; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = vk::SharingMode::eExclusive; + } + createInfo.preTransform = capabilities.currentTransform; + createInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; + createInfo.presentMode = presentMode; + createInfo.clipped = true; + createInfo.oldSwapchain = nullptr; + swapChain = device.createSwapchainKHR(createInfo); + swapChainImages = device.getSwapchainImagesKHR(swapChain); + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; +} + +void VulkanTest::cleanupSwapChain() const { + for (const auto& framebuffer : swapChainFramebuffers) { + device.destroyFramebuffer(framebuffer); + } + for (const auto& imageView : swapChainImageViews) { + device.destroyImageView(imageView); + } + device.destroySwapchainKHR(swapChain); +} + +void VulkanTest::recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + createFramebuffers(); +} + +void VulkanTest::createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + for (size_t i = 0; i < swapChainImages.size(); i++) { + vk::ImageViewCreateInfo createInfo{}; + createInfo.image = swapChainImages[i]; + createInfo.viewType = vk::ImageViewType::e2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = vk::ComponentSwizzle::eIdentity; + createInfo.components.g = vk::ComponentSwizzle::eIdentity; + createInfo.components.b = vk::ComponentSwizzle::eIdentity; + createInfo.components.a = vk::ComponentSwizzle::eIdentity; + createInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (device.createImageView(&createInfo, nullptr, &swapChainImageViews[i]) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to create image views"); + } + } +} + +void VulkanTest::createRenderPass() { + vk::AttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = vk::SampleCountFlagBits::e1; + colorAttachment.loadOp = vk::AttachmentLoadOp::eClear; + colorAttachment.storeOp = vk::AttachmentStoreOp::eStore; + colorAttachment.stencilLoadOp = vk::AttachmentLoadOp::eDontCare; + colorAttachment.stencilStoreOp = vk::AttachmentStoreOp::eDontCare; + colorAttachment.initialLayout = vk::ImageLayout::eUndefined; + colorAttachment.finalLayout = vk::ImageLayout::ePresentSrcKHR; + + vk::AttachmentReference colorAttachmentRef; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = vk::ImageLayout::eColorAttachmentOptimal; + + vk::SubpassDescription subpass{}; + subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + vk::SubpassDependency dependency{}; + dependency.srcSubpass = vk::SubpassExternal; + dependency.dstSubpass = 0; + dependency.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; + dependency.srcAccessMask = vk::AccessFlags(); + dependency.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; + dependency.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; + + vk::RenderPassCreateInfo renderPassInfo{}; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + renderPass = device.createRenderPass(renderPassInfo); +} + +void VulkanTest::createGraphicsPipeline() { + auto vertShaderCode = readFile("../assets/shaders/vulkan-test.vert.spv"); + auto fragShaderCode = readFile("../assets/shaders/vulkan-test.frag.spv"); + + vk::ShaderModule vertShaderModule = createShaderModule(vertShaderCode); + vk::ShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + vk::PipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.stage = vk::ShaderStageFlagBits::eVertex; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + vk::PipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.stage = vk::ShaderStageFlagBits::eFragment; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + vk::PipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.vertexBindingDescriptionCount = 0; + vertexInputInfo.pVertexBindingDescriptions = nullptr; + vertexInputInfo.vertexAttributeDescriptionCount = 0; + vertexInputInfo.pVertexAttributeDescriptions = nullptr; + + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.topology = vk::PrimitiveTopology::eTriangleList; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + vk::Viewport viewport = {0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0}; + + vk::Rect2D scissor = {{0, 0}, swapChainExtent}; + + const std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor, + }; + vk::PipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + vk::PipelineViewportStateCreateInfo viewportState{}; + viewportState.viewportCount = 1; + viewportState.pViewports = &viewport; + viewportState.scissorCount = 1; + viewportState.pScissors = &scissor; + + vk::PipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = vk::PolygonMode::eFill; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = vk::CullModeFlagBits::eBack; + rasterizer.frontFace = vk::FrontFace::eClockwise; + rasterizer.depthBiasEnable = VK_FALSE; + rasterizer.depthBiasConstantFactor = 0.0f; + rasterizer.depthBiasClamp = 0.0f; + rasterizer.depthBiasSlopeFactor = 0.0f; + + vk::PipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = vk::SampleCountFlagBits::e1; + multisampling.minSampleShading = 1.0f; + multisampling.pSampleMask = nullptr; + multisampling.alphaToCoverageEnable = VK_FALSE; + multisampling.alphaToOneEnable = VK_FALSE; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + colorBlendAttachment.blendEnable = VK_FALSE; + colorBlendAttachment.srcColorBlendFactor = vk::BlendFactor::eOne; + colorBlendAttachment.dstColorBlendFactor = vk::BlendFactor::eZero; + colorBlendAttachment.colorBlendOp = vk::BlendOp::eAdd; + colorBlendAttachment.srcAlphaBlendFactor = vk::BlendFactor::eOne; + colorBlendAttachment.dstAlphaBlendFactor = vk::BlendFactor::eZero; + colorBlendAttachment.alphaBlendOp = vk::BlendOp::eAdd; + + vk::PipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = vk::LogicOp::eCopy; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.setLayoutCount = 0; + pipelineLayoutInfo.pSetLayouts = nullptr; + pipelineLayoutInfo.pushConstantRangeCount = 0; + pipelineLayoutInfo.pPushConstantRanges = nullptr; + pipelineLayout = device.createPipelineLayout(pipelineLayoutInfo); + + vk::GraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pDepthStencilState = nullptr; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + + const vk::ResultValue result = device.createGraphicsPipeline(nullptr, pipelineInfo); + if (result.result != vk::Result::eSuccess) { + throw std::runtime_error("Failed to create graphics pipeline"); + } + graphicsPipeline = result.value; + + device.destroyShaderModule(fragShaderModule); + device.destroyShaderModule(vertShaderModule); +} + +void VulkanTest::createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + const vk::ImageView attachments[] = {swapChainImageViews[i]}; + vk::FramebufferCreateInfo framebufferInfo{}; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (device.createFramebuffer(&framebufferInfo, nullptr, &swapChainFramebuffers[i]) != vk::Result::eSuccess) { + throw std::runtime_error("failed to create framebuffer"); + } + } +} + +void VulkanTest::createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + vk::CommandPoolCreateInfo poolInfo{}; + poolInfo.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (device.createCommandPool(&poolInfo, nullptr, &commandPool) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to create command pool"); + } +} + +void VulkanTest::createCommandBuffers() { + vk::CommandBufferAllocateInfo allocInfo{}; + allocInfo.commandPool = commandPool; + allocInfo.level = vk::CommandBufferLevel::ePrimary; + allocInfo.commandBufferCount = maxFramesInFlight; + commandBuffers = device.allocateCommandBuffers(allocInfo); +} + +void VulkanTest::createSyncObjects() { + imageAvailableSemaphores.resize(maxFramesInFlight); + renderFinishedSemaphores.resize(maxFramesInFlight); + inFlightFences.resize(maxFramesInFlight); + + const vk::SemaphoreCreateInfo semaphoreInfo{}; + vk::FenceCreateInfo fenceInfo{}; + fenceInfo.flags = vk::FenceCreateFlagBits::eSignaled; + + for (size_t i = 0; i < maxFramesInFlight; i++) { + imageAvailableSemaphores[i] = device.createSemaphore(semaphoreInfo); + renderFinishedSemaphores[i] = device.createSemaphore(semaphoreInfo); + inFlightFences[i] = device.createFence(fenceInfo); + } +} + +void VulkanTest::recordCommandBuffer(const vk::CommandBuffer commandBuffer, const unsigned int imageIndex) const { + constexpr vk::CommandBufferBeginInfo beginInfo{}; + if (commandBuffer.begin(&beginInfo) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to begin recording command buffer"); + } + vk::RenderPassBeginInfo renderPassInfo{}; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = vk::Offset2D(0, 0); + renderPassInfo.renderArea.extent = swapChainExtent; + constexpr vk::ClearValue clearColor = vk::ClearColorValue(std::array{0.0f, 0.0f, 0.0f, 1.0f}); + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + commandBuffer.beginRenderPass(&renderPassInfo, vk::SubpassContents::eInline); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D({0, 0}, swapChainExtent)); + commandBuffer.draw(3, 1, 0, 0); + commandBuffer.endRenderPass(); + commandBuffer.end(); +} + +vk::ShaderModule VulkanTest::createShaderModule(const std::vector& code) const { + vk::ShaderModuleCreateInfo createInfo{}; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + return device.createShaderModule(createInfo); +} + +VulkanTest::QueueFamilyIndices VulkanTest::findQueueFamilies(const vk::PhysicalDevice device) const { + QueueFamilyIndices indices; + std::vector queueFamilies = device.getQueueFamilyProperties(); + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & vk::QueueFlagBits::eGraphics) { + indices.graphicsFamily = i; + } + vk::Bool32 presentSupport = false; + if (device.getSurfaceSupportKHR(i, surface, &presentSupport) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to get device surface support"); + } + if (presentSupport) { + indices.presentFamily = i; + } + if (indices.isComplete()) { + break; + } + i++; + } + return indices; +} + +bool VulkanTest::checkDeviceExtensionSupport(const vk::PhysicalDevice device) const { + const std::vector availableExtensions = device.enumerateDeviceExtensionProperties(); + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + return requiredExtensions.empty(); +} + +VulkanTest::SwapChainSupportDetails VulkanTest::querySwapChainSupport(vk::PhysicalDevice device) const { + return { + .capabilities = device.getSurfaceCapabilitiesKHR(surface), + .formats = device.getSurfaceFormatsKHR(surface), + .presentModes = device.getSurfacePresentModesKHR(surface), + }; +} + +vk::SurfaceFormatKHR VulkanTest::chooseSwapSurfaceFormat(const std::vector& availableFormats) const { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == vk::Format::eB8G8R8A8Srgb && availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) { + return availableFormat; + } + } + return availableFormats[0]; +} + +vk::PresentModeKHR VulkanTest::chooseSwapPresentMode(const std::vector& availablePresentModes) const { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == vk::PresentModeKHR::eMailbox) { + return availablePresentMode; + } + } + return vk::PresentModeKHR::eFifo; +} + +vk::Extent2D VulkanTest::chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) const { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + int width, height; + glfwGetFramebufferSize(window, &width, &height); + vk::Extent2D actualExtent; + actualExtent.width = std::clamp(static_cast(width), capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(static_cast(height), capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + return actualExtent; +} + +unsigned int VulkanTest::rateDeviceSuitability(const vk::PhysicalDevice device) { + if (!device.getFeatures().geometryShader) { + return 0; + } + + const QueueFamilyIndices indices = findQueueFamilies(device); + if (!indices.isComplete()) { + return 0; + } + + if (!checkDeviceExtensionSupport(device)) { + return 0; + } + + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + if (swapChainSupport.formats.empty() || swapChainSupport.presentModes.empty()) { + return 0; + } + + const vk::PhysicalDeviceProperties properties = device.getProperties(); + unsigned int score = 0; + + if (properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu) { + score += 1000; + } + + score += properties.limits.maxImageDimension2D; + + if (indices.graphicsFamily == indices.presentFamily) { + score += 1; + } + + return score; +} + +std::vector VulkanTest::readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("Failed to open file"); + } + const auto fileSize = file.tellg(); + std::vector buffer(fileSize); + file.seekg(0); + file.read(buffer.data(), fileSize); + file.close(); + return buffer; +} diff --git a/engine/src/VulkanTest.h b/engine/src/VulkanTest.h new file mode 100644 index 0000000..6b9268d --- /dev/null +++ b/engine/src/VulkanTest.h @@ -0,0 +1,96 @@ +#pragma once +#include +#include + +class VulkanTest { +public: + void run(); + +private: + void initWindow(); + void initVulkan(); + void cleanup() const; + void mainLoop(); + void drawFrame(); + + void createInstance(); + bool checkValidationLayerSupport() const; + void pickPhysicalDevice(); + void createLogicalDevice(); + void createSurface(); + void createSwapChain(); + void cleanupSwapChain() const; + void recreateSwapChain(); + void createImageViews(); + void createRenderPass(); + void createGraphicsPipeline(); + void createFramebuffers(); + void createCommandPool(); + void createCommandBuffers(); + void createSyncObjects(); + + void recordCommandBuffer(vk::CommandBuffer commandBuffer, unsigned int imageIndex) const; + + unsigned int rateDeviceSuitability(vk::PhysicalDevice device); + + struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() const { + return graphicsFamily.has_value() && presentFamily.has_value(); + } + }; + + QueueFamilyIndices findQueueFamilies(vk::PhysicalDevice device) const; + bool checkDeviceExtensionSupport(vk::PhysicalDevice device) const; + + struct SwapChainSupportDetails { + vk::SurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; + }; + + SwapChainSupportDetails querySwapChainSupport(vk::PhysicalDevice device) const; + vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) const; + vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) const; + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) const; + + vk::ShaderModule createShaderModule(const std::vector& code) const; + + static std::vector readFile(const std::string& filename); + + GLFWwindow* window = nullptr; + vk::Instance instance; + vk::PhysicalDevice physicalDevice = nullptr; + vk::Device device; + vk::Queue graphicsQueue; + vk::Queue presentQueue; + vk::SurfaceKHR surface; + vk::SwapchainKHR swapChain; + std::vector swapChainImages; + vk::Format swapChainImageFormat = {}; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + vk::RenderPass renderPass; + vk::PipelineLayout pipelineLayout; + vk::Pipeline graphicsPipeline; + std::vector swapChainFramebuffers; + vk::CommandPool commandPool; + + static constexpr unsigned int maxFramesInFlight = 2; + std::vector commandBuffers; + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + unsigned int currentFrame = 0; + + const std::vector validationLayers = {"VK_LAYER_KHRONOS_validation"}; + const std::vector deviceExtensions = {vk::KHRSwapchainExtensionName}; + +#if defined(DE_DEBUG) && (!defined(DE_PLATFORM_WINDOWS) || defined(VK_ADD_LAYER_PATH)) + const bool enableValidationLayers = true; +#else + const bool enableValidationLayers = false; +#endif +}; diff --git a/engine/src/cef/OSRCefHandler.h b/engine/src/cef/OSRCefHandler.h index 5c1bd54..d3bb830 100644 --- a/engine/src/cef/OSRCefHandler.h +++ b/engine/src/cef/OSRCefHandler.h @@ -12,7 +12,7 @@ class OSRCefHandler : public CefClient, public CefRenderHandler, public CefLoadH explicit OSRCefHandler(const Ref& app); ~OSRCefHandler() override; - void setTexture(const Ref& texture) { + void setTexture(const Ref& texture) { this->texture = texture; } @@ -106,7 +106,7 @@ class OSRCefHandler : public CefClient, public CefRenderHandler, public CefLoadH } textureInfo; std::mutex textureInfoMutex; #endif - Ref texture; + Ref texture; CefRefPtr host; BrowserMessageHandler browserMessageHandler = BrowserMessageHandler(); bool closing = false; diff --git a/engine/src/editor/scripts/EditorScript.cpp b/engine/src/editor/scripts/EditorScript.cpp index 91ec203..a728c86 100644 --- a/engine/src/editor/scripts/EditorScript.cpp +++ b/engine/src/editor/scripts/EditorScript.cpp @@ -39,7 +39,7 @@ EditorScript::EditorScript(const Ref& app, const Ref& scene, return; // mouse is outside the viewport } - const int entityId = app->getRenderer()->getFramebuffer()->getMousePickingValue(mouseX, mouseY); + const int entityId = app->getRenderer()->readPixelIntSync(app->getRenderer()->getMousePickingFramebuffer(), mouseX, mouseY, 0); if (entityId >= 0) { const Ref& selectedEntity = this->scene->getEntity(entityId); if (selectedEntity->has()) { diff --git a/engine/src/editor/scripts/UIScript.h b/engine/src/editor/scripts/UIScript.h index 271cb89..a62df6b 100644 --- a/engine/src/editor/scripts/UIScript.h +++ b/engine/src/editor/scripts/UIScript.h @@ -33,5 +33,5 @@ class UIScript final : public EntityScript { }; struct UITexture { - Ref texture; + Ref texture; }; diff --git a/engine/src/events/Dispatcher.h b/engine/src/events/Dispatcher.h index fb841d3..54cb14d 100644 --- a/engine/src/events/Dispatcher.h +++ b/engine/src/events/Dispatcher.h @@ -1,6 +1,5 @@ #pragma once -#include "pch/enginepch.h" #include "Event.h" // ReSharper disable CppMemberFunctionMayBeStatic diff --git a/engine/src/images/Image.cpp b/engine/src/images/Image.cpp index bcfd0f2..45399eb 100644 --- a/engine/src/images/Image.cpp +++ b/engine/src/images/Image.cpp @@ -1,44 +1,15 @@ #include "pch/enginepch.h" #include "Image.h" -#include -Image::Image(const std::string& path) { - const bool isHDR = path.ends_with(".hdr"); - int width; - int height; - int channels; +Image::Image(const unsigned int width, const unsigned int height, const TextureFormat format, const TextureInternalFormat internalFormat, std::unique_ptr data) : + width(width), height(height), format(format), internalFormat(internalFormat), data(std::move(data)) {} - stbi_set_flip_vertically_on_load(true); - void* texture; - if (isHDR) { - texture = stbi_loadf(path.c_str(), &width, &height, &channels, 0); - } else { - texture = stbi_load(path.c_str(), &width, &height, &channels, 0); - } - - if (!texture) { - const char* error = stbi_failure_reason(); - DE_ERROR("Failed to read texture file {0} - {1}", path, error); - } - - this->width = width; - this->height = height; - this->channels = channels; - this->bytesPerPixel = isHDR ? sizeof(float) * this->channels : sizeof(uint8_t); - this->data = std::make_unique(width * height * bytesPerPixel); - memcpy(this->data.get(), texture, width * height * bytesPerPixel); - - stbi_image_free(texture); -} - -Image::Image(const unsigned int width, const unsigned int height, const unsigned int channels, const unsigned int bytesPerPixel, const void* data) : width(width), height(height), channels(channels), bytesPerPixel(bytesPerPixel) { - this->data = std::make_unique(width * height * bytesPerPixel); - if (data) { - memcpy(this->data.get(), data, width * height * bytesPerPixel); - } +Image::Image(const unsigned int width, const unsigned int height, const TextureFormat format, const TextureInternalFormat internalFormat) : + width(width), height(height), format(format), internalFormat(internalFormat) { + this->data = std::make_unique(getDataSize()); } void* Image::getPixelPointer(const unsigned int x, const unsigned int y) const { - return this->data.get() + static_cast((y * this->width + x) * this->bytesPerPixel); + return this->data.get() + static_cast((y * this->width + x) * this->internalFormat.getSize()); } diff --git a/engine/src/images/Image.h b/engine/src/images/Image.h index f408358..1118a2c 100644 --- a/engine/src/images/Image.h +++ b/engine/src/images/Image.h @@ -1,20 +1,24 @@ #pragma once +/** + * The Image class is similar to Texture but stores the image data in CPU memory. + * It cannot be used directly for rendering. + */ class Image { public: Image() = default; - explicit Image(const std::string& path); - Image(unsigned int width, unsigned int height, unsigned int channels, unsigned int bytesPerPixel, const void* data); + Image(unsigned int width, unsigned int height, TextureFormat format, TextureInternalFormat internalFormat, std::unique_ptr data); + Image(unsigned int width, unsigned int height, TextureFormat format, TextureInternalFormat internalFormat); - Image(Image&& other) noexcept : width(other.width), height(other.height), channels(other.channels), bytesPerPixel(other.bytesPerPixel), data(std::move(other.data)) {} + Image(Image&& other) noexcept : width(other.width), height(other.height), format(other.format), internalFormat(other.internalFormat), data(std::move(other.data)) {} Image& operator=(Image&& other) noexcept { if (this == &other) return *this; - width = other.width; - height = other.height; - channels = other.channels; - bytesPerPixel = other.bytesPerPixel; - data = std::move(other.data); + this->width = other.width; + this->height = other.height; + this->format = other.format; + this->internalFormat = other.internalFormat; + this->data = std::move(other.data); return *this; } @@ -22,23 +26,27 @@ class Image { unsigned int getWidth() const { - return width; + return this->width; } unsigned int getHeight() const { - return height; + return this->height; } - unsigned int getChannels() const { - return channels; + TextureFormat getFormat() const { + return this->format; } - unsigned int getBytesPerPixel() const { - return bytesPerPixel; + TextureInternalFormat getInternalFormat() const { + return this->internalFormat; } - void* getData() const { - return data.get(); + const std::unique_ptr& getData() const { + return this->data; + } + + unsigned int getDataSize() const { + return this->width * this->height * this->internalFormat.getSize(); } void* getPixelPointer(unsigned int x, unsigned int y) const; @@ -46,7 +54,7 @@ class Image { protected: unsigned int width; unsigned int height; - unsigned int channels; - unsigned int bytesPerPixel; + TextureFormat format; + TextureInternalFormat internalFormat; std::unique_ptr data; }; diff --git a/engine/src/images/ImageUtils.cpp b/engine/src/images/ImageUtils.cpp index f143761..85217a3 100644 --- a/engine/src/images/ImageUtils.cpp +++ b/engine/src/images/ImageUtils.cpp @@ -1,19 +1,71 @@ #include "pch/enginepch.h" #include "ImageUtils.h" +#include + +Ref ImageUtils::loadImageFromFile(const std::string& path) { + unsigned int width; + unsigned int height; + TextureFormat format; + TextureInternalFormat internalFormat; + std::unique_ptr data = loadImageData(path, width, height, format, internalFormat); + return std::make_shared(width, height, format, internalFormat, std::move(data)); +} + +Ref ImageUtils::loadTextureFromFile(const Ref& renderer, const std::string& path) { + unsigned int width; + unsigned int height; + TextureFormat format; + TextureInternalFormat internalFormat; + std::unique_ptr data = loadImageData(path, width, height, format, internalFormat); + return Texture::builder() // + .width(width) + .height(height) + .format(format) + .internalFormat(internalFormat) + .type(TextureType::TEXTURE_2D) + .data(std::move(data)) + .build(renderer); +} + +/** + * Always remember to free the returned data using stbi_image_free when done using it. + */ +std::unique_ptr ImageUtils::loadImageData(const std::string& path, unsigned int& width, unsigned int& height, TextureFormat& format, + TextureInternalFormat& internalFormat) { + int w, h; + int channels; + + stbi_set_flip_vertically_on_load(true); + uint8_t* data = stbi_load(path.c_str(), &w, &h, &channels, 0); + + if (!data) { + const char* error = stbi_failure_reason(); + DE_ERROR("Failed to read texture file {0} - {1}", path, error); + } + + width = static_cast(w); + height = static_cast(h); + format = channels > 3 ? TextureFormat::RGBA : TextureFormat::RGB; + internalFormat = channels > 3 ? TextureInternalFormat::RGBA8 : TextureInternalFormat::RGB8; + return std::unique_ptr(data); +} + Ref ImageUtils::acesFilmicTonemapping(const Ref& image) { - auto result = std::make_shared(image->getWidth(), image->getHeight(), nullptr); + auto result = std::make_shared(image->getWidth(), image->getHeight()); for (unsigned int x = 0; x < image->getWidth(); x++) { for (unsigned int y = 0; y < image->getHeight(); y++) { const auto originalPixel = static_cast(image->getPixelPointer(x, y)); const auto resultPixel = static_cast(result->getPixelPointer(x, y)); const auto color = glm::vec3(originalPixel[0], originalPixel[1], originalPixel[2]); + const auto alpha = originalPixel[3]; constexpr float a = 2.51f; constexpr float b = 0.03f; constexpr float c = 2.43f; constexpr float d = 0.59f; constexpr float e = 0.14f; - *reinterpret_cast(resultPixel) = glm::clamp((color * (a * color + b)) / (color * (c * color + d) + e), glm::vec3(0.0), glm::vec3(1.0)); + const glm::vec3 mapped = glm::clamp((color * (a * color + b)) / (color * (c * color + d) + e), glm::vec3(0.0), glm::vec3(1.0)); + *reinterpret_cast(resultPixel) = glm::vec4(mapped, alpha); } } return result; diff --git a/engine/src/images/ImageUtils.h b/engine/src/images/ImageUtils.h index 5bb6395..98bb7b3 100644 --- a/engine/src/images/ImageUtils.h +++ b/engine/src/images/ImageUtils.h @@ -3,5 +3,13 @@ class ImageUtils { public: + static Ref loadImageFromFile(const std::string& path); + static Ref loadTextureFromFile(const Ref& renderer, const std::string& path); + static std::unique_ptr loadImageData(const std::string& path, unsigned int& width, unsigned int& height, TextureFormat& format, TextureInternalFormat& internalFormat); static Ref acesFilmicTonemapping(const Ref& image); + +private: + static Ref createImageFromData(unsigned int width, unsigned int height, TextureFormat format, TextureInternalFormat internalFormat, std::unique_ptr data); + static Ref createTextureFromData(const Ref& renderer, unsigned int width, unsigned int height, TextureFormat format, + TextureInternalFormat internalFormat, std::unique_ptr data); }; diff --git a/engine/src/images/LinearImage.cpp b/engine/src/images/LinearImage.cpp index afc7b3a..06abf29 100644 --- a/engine/src/images/LinearImage.cpp +++ b/engine/src/images/LinearImage.cpp @@ -3,7 +3,7 @@ #include -LinearImage::LinearImage(const std::string& path) : Image(0, 0, 3, sizeof(float) * 3, nullptr) { +LinearImage::LinearImage(const std::string& path) : Image(0, 0, TextureFormat::RGBA, TextureInternalFormat::RGBA32_FLOAT, nullptr) { auto imageStream = std::ifstream(path, std::ios::in | std::ios::binary); if (!imageStream.is_open()) { DE_ERROR("Failed to open image file {0}", path); @@ -47,10 +47,10 @@ LinearImage::LinearImage(const std::string& path) : Image(0, 0, 3, sizeof(float) return; } - this->data = std::make_unique(width * height * this->bytesPerPixel); + this->data = std::make_unique(static_cast(width) * height * this->internalFormat.getSize()); std::vector row; - row.reserve(width * 4); // RGBE + row.resize(width * 4); // RGBE // read the first 3 bytes of the file to check if the file is using RLE compression const std::streampos position = imageStream.tellg(); @@ -107,7 +107,7 @@ LinearImage::LinearImage(const std::string& path) : Image(0, 0, 3, sizeof(float) } } for (unsigned int x = 0; x < width; x++) { - convertRGBEtoRGB(row.data() + x * 4, reinterpret_cast(this->data.get()) + (y * width + x) * 3); + convertRGBEtoRGB(row.data() + x * 4, reinterpret_cast(this->data.get()) + (y * width + x) * 4); // TODO: replace 4 with dynamic value } } } else { @@ -117,21 +117,23 @@ LinearImage::LinearImage(const std::string& path) : Image(0, 0, 3, sizeof(float) // invert the image vertically // this doubles the memory usage, use a different approach if memory is a concern - auto invertedData = std::make_unique(width * height * this->bytesPerPixel); - unsigned int bytesPerRow = this->bytesPerPixel * width; + const unsigned int bbp = this->internalFormat.getSize(); + auto invertedData = std::make_unique(width * height * bbp); + unsigned int bytesPerRow = bbp * width; for (unsigned int y = 0; y < height; y++) { - float* src = reinterpret_cast(this->data.get()) + (height - y - 1) * width * 3; // NOLINT(bugprone-implicit-widening-of-multiplication-result) - float* dst = reinterpret_cast(invertedData.get()) + y * width * 3; // NOLINT(bugprone-implicit-widening-of-multiplication-result) + // TODO: replace 4 with dynamic value + float* src = reinterpret_cast(this->data.get()) + (height - y - 1) * width * 4; // NOLINT(bugprone-implicit-widening-of-multiplication-result) + float* dst = reinterpret_cast(invertedData.get()) + y * width * 4; // NOLINT(bugprone-implicit-widening-of-multiplication-result) std::memcpy(dst, src, bytesPerRow); } this->data = std::move(invertedData); } -LinearImage::LinearImage(const unsigned int width, const unsigned int height, const float* data, const float gamma, const float exposure) : - Image(width, height, 3, sizeof(float) * 3, data) { - this->gamma = gamma; - this->exposure = exposure; -} +LinearImage::LinearImage(const unsigned int width, const unsigned int height, std::unique_ptr data, const float gamma, const float exposure) : + Image(width, height, TextureFormat::RGBA, TextureInternalFormat::RGBA32_FLOAT, std::move(data)), gamma(gamma), exposure(exposure) {} + +LinearImage::LinearImage(const unsigned int width, const unsigned int height, const float gamma, const float exposure) : + Image(width, height, TextureFormat::RGBA, TextureInternalFormat::RGBA32_FLOAT), gamma(gamma), exposure(exposure) {} void LinearImage::convertRGBEtoRGB(const unsigned char* rgbe, float* rgb) { if (rgbe[3] == 0) { diff --git a/engine/src/images/LinearImage.h b/engine/src/images/LinearImage.h index 6d18146..e1bc695 100644 --- a/engine/src/images/LinearImage.h +++ b/engine/src/images/LinearImage.h @@ -1,10 +1,12 @@ #pragma once +#include "Image.h" class LinearImage final : public Image { public: LinearImage() = default; explicit LinearImage(const std::string& path); - LinearImage(unsigned int width, unsigned int height, const float* data, float gamma = 1.0f, float exposure = 1.0f); + LinearImage(unsigned int width, unsigned int height, std::unique_ptr data, float gamma = 1.0f, float exposure = 1.0f); + LinearImage(unsigned int width, unsigned int height, float gamma = 1.0f, float exposure = 1.0f); private: diff --git a/engine/src/layers/EditorLayer.cpp b/engine/src/layers/EditorLayer.cpp index f4081d3..ca5fbe5 100644 --- a/engine/src/layers/EditorLayer.cpp +++ b/engine/src/layers/EditorLayer.cpp @@ -5,7 +5,10 @@ EditorLayer::EditorLayer(const std::unique_ptr& ctx) : Layer(ctx) { Ref uiEntity = this->scene->createEntity("CEF Editor UI"); - const auto uiMaterial = Material(ctx->renderer->createTexture2D(4, 1, 1, 1, BGRA, std::array{0, 0, 0, 0}.data())); + auto transparent = std::make_unique(4); + std::memcpy(transparent.get(), std::array{0, 0, 0, 0}.data(), 4); + const auto uiMaterial = + Material(Texture::builder().width(1).height(1).format(TextureFormat::BGRA).internalFormat(TextureInternalFormat::RGBA8).data(std::move(transparent)).build(ctx->renderer)); this->uiMesh = Plane::create(ctx->renderer, uiMaterial); uiEntity->add(uiMaterial.albedo); Script& uiScript = uiEntity->add