initial commit

This commit is contained in:
2024-04-06 14:11:26 +02:00
commit 1d44ecc0ee
85 changed files with 11573 additions and 0 deletions

26
source/addon.cpp Normal file
View File

@@ -0,0 +1,26 @@
#include "iwa/addon.hpp"
#include <vector>
namespace iwa
{
namespace
{
std::vector<Addon*>& getAddonsVector() noexcept
{
static std::vector<Addon*> addons;
return addons;
}
}
Addon::Addon()
{
getAddonsVector().push_back(this);
}
std::span<Addon* const> getAddons() noexcept
{
return getAddonsVector();
}
} // namespace iwa

View File

@@ -0,0 +1,20 @@
Import('env')
# Imgui
lib_imgui = env.Cook('imgui', backends = ['vulkan', 'sdl2'], git_ref = 'refs/tags/v1.90')
src_files = Split("""
fps_widget.cpp
""")
add_src_files = [env.File('addon.cpp')]
lib_iwa_imgui = env.UnityStaticLibrary(
target = env['LIB_DIR'] + '/iwa_imgui',
source = src_files,
add_source = add_src_files,
dependencies = [env['lib_iwa'], lib_imgui]
)
env['lib_iwa_imgui'] = lib_iwa_imgui
Return('env')

View File

@@ -0,0 +1,299 @@
#include "iwa/addons/imgui/addon.hpp"
#include <imgui.h>
#include <backends/imgui_impl_sdl2.h>
#include <backends/imgui_impl_vulkan.h>
namespace iwa
{
namespace
{
ImGuiAddon gImguiAddon;
PFN_vkVoidFunction imguiLoaderCallback(const char* functionName, void* userData)
{
return static_cast<Instance*>(userData)->getVkHandle().getProcAddr(functionName);
}
ImGuiKey keyToImGui(const KeyCode keyCode)
{
switch (keyCode)
{
//<editor-fold desc="Many cases">
case KeyCode::TAB: return ImGuiKey_Tab;
case KeyCode::LEFT: return ImGuiKey_LeftArrow;
case KeyCode::RIGHT: return ImGuiKey_RightArrow;
case KeyCode::UP: return ImGuiKey_UpArrow;
case KeyCode::DOWN: return ImGuiKey_DownArrow;
case KeyCode::PAGEUP: return ImGuiKey_PageUp;
case KeyCode::PAGEDOWN: return ImGuiKey_PageDown;
case KeyCode::HOME: return ImGuiKey_Home;
case KeyCode::END: return ImGuiKey_End;
case KeyCode::INSERT: return ImGuiKey_Insert;
case KeyCode::DELETE: return ImGuiKey_Delete;
case KeyCode::BACKSPACE: return ImGuiKey_Backspace;
case KeyCode::SPACE: return ImGuiKey_Space;
case KeyCode::RETURN: return ImGuiKey_Enter;
case KeyCode::ESCAPE: return ImGuiKey_Escape;
case KeyCode::QUOTE: return ImGuiKey_Apostrophe;
case KeyCode::COMMA: return ImGuiKey_Comma;
case KeyCode::MINUS: return ImGuiKey_Minus;
case KeyCode::PERIOD: return ImGuiKey_Period;
case KeyCode::SLASH: return ImGuiKey_Slash;
case KeyCode::SEMICOLON: return ImGuiKey_Semicolon;
case KeyCode::EQUALS: return ImGuiKey_Equal;
case KeyCode::LEFTBRACKET: return ImGuiKey_LeftBracket;
case KeyCode::BACKSLASH: return ImGuiKey_Backslash;
case KeyCode::RIGHTBRACKET: return ImGuiKey_RightBracket;
case KeyCode::BACKQUOTE: return ImGuiKey_GraveAccent;
case KeyCode::CAPSLOCK: return ImGuiKey_CapsLock;
case KeyCode::SCROLLLOCK: return ImGuiKey_ScrollLock;
case KeyCode::NUMLOCKCLEAR: return ImGuiKey_NumLock;
case KeyCode::PRINTSCREEN: return ImGuiKey_PrintScreen;
case KeyCode::PAUSE: return ImGuiKey_Pause;
case KeyCode::KP_0: return ImGuiKey_Keypad0;
case KeyCode::KP_1: return ImGuiKey_Keypad1;
case KeyCode::KP_2: return ImGuiKey_Keypad2;
case KeyCode::KP_3: return ImGuiKey_Keypad3;
case KeyCode::KP_4: return ImGuiKey_Keypad4;
case KeyCode::KP_5: return ImGuiKey_Keypad5;
case KeyCode::KP_6: return ImGuiKey_Keypad6;
case KeyCode::KP_7: return ImGuiKey_Keypad7;
case KeyCode::KP_8: return ImGuiKey_Keypad8;
case KeyCode::KP_9: return ImGuiKey_Keypad9;
case KeyCode::KP_PERIOD: return ImGuiKey_KeypadDecimal;
case KeyCode::KP_DIVIDE: return ImGuiKey_KeypadDivide;
case KeyCode::KP_MULTIPLY: return ImGuiKey_KeypadMultiply;
case KeyCode::KP_MINUS: return ImGuiKey_KeypadSubtract;
case KeyCode::KP_PLUS: return ImGuiKey_KeypadAdd;
case KeyCode::KP_ENTER: return ImGuiKey_KeypadEnter;
case KeyCode::KP_EQUALS: return ImGuiKey_KeypadEqual;
case KeyCode::LCTRL: return ImGuiKey_LeftCtrl;
case KeyCode::LSHIFT: return ImGuiKey_LeftShift;
case KeyCode::LALT: return ImGuiKey_LeftAlt;
case KeyCode::LGUI: return ImGuiKey_LeftSuper;
case KeyCode::RCTRL: return ImGuiKey_RightCtrl;
case KeyCode::RSHIFT: return ImGuiKey_RightShift;
case KeyCode::RALT: return ImGuiKey_RightAlt;
case KeyCode::RGUI: return ImGuiKey_RightSuper;
case KeyCode::APPLICATION: return ImGuiKey_Menu;
case KeyCode::_0: return ImGuiKey_0;
case KeyCode::_1: return ImGuiKey_1;
case KeyCode::_2: return ImGuiKey_2;
case KeyCode::_3: return ImGuiKey_3;
case KeyCode::_4: return ImGuiKey_4;
case KeyCode::_5: return ImGuiKey_5;
case KeyCode::_6: return ImGuiKey_6;
case KeyCode::_7: return ImGuiKey_7;
case KeyCode::_8: return ImGuiKey_8;
case KeyCode::_9: return ImGuiKey_9;
case KeyCode::A: return ImGuiKey_A;
case KeyCode::B: return ImGuiKey_B;
case KeyCode::C: return ImGuiKey_C;
case KeyCode::D: return ImGuiKey_D;
case KeyCode::E: return ImGuiKey_E;
case KeyCode::F: return ImGuiKey_F;
case KeyCode::G: return ImGuiKey_G;
case KeyCode::H: return ImGuiKey_H;
case KeyCode::I: return ImGuiKey_I;
case KeyCode::J: return ImGuiKey_J;
case KeyCode::K: return ImGuiKey_K;
case KeyCode::L: return ImGuiKey_L;
case KeyCode::M: return ImGuiKey_M;
case KeyCode::N: return ImGuiKey_N;
case KeyCode::O: return ImGuiKey_O;
case KeyCode::P: return ImGuiKey_P;
case KeyCode::Q: return ImGuiKey_Q;
case KeyCode::R: return ImGuiKey_R;
case KeyCode::S: return ImGuiKey_S;
case KeyCode::T: return ImGuiKey_T;
case KeyCode::U: return ImGuiKey_U;
case KeyCode::V: return ImGuiKey_V;
case KeyCode::W: return ImGuiKey_W;
case KeyCode::X: return ImGuiKey_X;
case KeyCode::Y: return ImGuiKey_Y;
case KeyCode::Z: return ImGuiKey_Z;
case KeyCode::F1: return ImGuiKey_F1;
case KeyCode::F2: return ImGuiKey_F2;
case KeyCode::F3: return ImGuiKey_F3;
case KeyCode::F4: return ImGuiKey_F4;
case KeyCode::F5: return ImGuiKey_F5;
case KeyCode::F6: return ImGuiKey_F6;
case KeyCode::F7: return ImGuiKey_F7;
case KeyCode::F8: return ImGuiKey_F8;
case KeyCode::F9: return ImGuiKey_F9;
case KeyCode::F10: return ImGuiKey_F10;
case KeyCode::F11: return ImGuiKey_F11;
case KeyCode::F12: return ImGuiKey_F12;
case KeyCode::F13: return ImGuiKey_F13;
case KeyCode::F14: return ImGuiKey_F14;
case KeyCode::F15: return ImGuiKey_F15;
case KeyCode::F16: return ImGuiKey_F16;
case KeyCode::F17: return ImGuiKey_F17;
case KeyCode::F18: return ImGuiKey_F18;
case KeyCode::F19: return ImGuiKey_F19;
case KeyCode::F20: return ImGuiKey_F20;
case KeyCode::F21: return ImGuiKey_F21;
case KeyCode::F22: return ImGuiKey_F22;
case KeyCode::F23: return ImGuiKey_F23;
case KeyCode::F24: return ImGuiKey_F24;
case KeyCode::AC_BACK: return ImGuiKey_AppBack;
case KeyCode::AC_FORWARD: return ImGuiKey_AppForward;
default: return ImGuiKey_None;
//</editor-fold>
}
}
ImGuiMouseButton mouseButtonToImGui(const MouseButton button)
{
switch (button)
{
case MouseButton::LEFT:
return ImGuiMouseButton_Left;
case MouseButton::RIGHT:
return ImGuiMouseButton_Right;
case MouseButton::MIDDLE:
return ImGuiMouseButton_Middle;
case MouseButton::EXTRA_1:
return ImGuiMouseButton_Middle + 1;
case MouseButton::EXTRA_2:
return ImGuiMouseButton_Middle + 2;
}
return -1;
}
}
void ImGuiAddon::init(const AddonInitArgs& args)
{
(void) args;
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGui::StyleColorsDark();
}
void ImGuiAddon::cleanup()
{
ImGui::DestroyContext();
}
mijin::Task<> ImGuiAddon::c_createResources(const ImguiCreateResourcesArgs& args)
{
const unsigned QUEUED_FRAMES = 3; // TODO: is this okay?
const unsigned MAX_IMAGES = 256;
Device& device = *args.swapchain.getOwner();
mDescriptorPool = device.createChild<DescriptorPool>(DescriptorPoolCreationArgs{
.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet,
.maxSets = MAX_IMAGES,
.poolSizes = {
vk::DescriptorPoolSize{.type = vk::DescriptorType::eCombinedImageSampler, .descriptorCount = MAX_IMAGES}
}
});
registerObjectDestructionHandler(args.swapchain, [this, device = device.getPointer()]
{
// destroy the descriptor pool when the window is destroyed so we don't keep the device alive
device->getVkHandle().waitIdle();
ImGui_ImplVulkan_Shutdown();
ImGui_ImplSDL2_Shutdown();
mDescriptorPool = nullptr;
mWidgets.clear();
});
if (!ImGui_ImplSDL2_InitForVulkan(args.swapchain.getWindow()->getSDLWindow())) {
throw std::runtime_error("Error initializing ImGui for SDL2.");
}
ImGui_ImplVulkan_InitInfo initInfo{
.Instance = device.getOwner()->getVkHandle(),
.PhysicalDevice = device.getVkPhysicalDevice(),
.Device = device.getVkHandle(),
.QueueFamily = device.getDeviceInfo().graphicsQueueFamily,
.Queue = device.getGraphicsQueue(),
.PipelineCache = VK_NULL_HANDLE,
.DescriptorPool = mDescriptorPool->getVkHandle(),
.Subpass = 0,
.MinImageCount = QUEUED_FRAMES,
.ImageCount = QUEUED_FRAMES,
.MSAASamples = VK_SAMPLE_COUNT_1_BIT,
.UseDynamicRendering = true,
.ColorAttachmentFormat = static_cast<VkFormat>(args.format),
.Allocator = nullptr,
.CheckVkResultFn = nullptr,
};
const bool success = ImGui_ImplVulkan_LoadFunctions(imguiLoaderCallback, device.getOwner())
&& ImGui_ImplVulkan_Init(&initInfo, VK_NULL_HANDLE);
if (!success) {
throw std::runtime_error("Error initializing ImGui for Vulkan.");
}
// setup input
args.swapchain.getWindow()->keyChanged.connect(*this, &ImGuiAddon::handleKeyChanged);
args.swapchain.getWindow()->mouseButtonChanged.connect(*this, &ImGuiAddon::handleMouseButtonChanged);
args.swapchain.getWindow()->mouseMoved.connect(*this, &ImGuiAddon::handleMouseMoved);
args.swapchain.getWindow()->mouseScrolled.connect(*this, &ImGuiAddon::handleMouseScrolled);
args.swapchain.getWindow()->textEntered.connect(*this, &ImGuiAddon::handleTextEntered);
// first frame
beginFrame();
co_return;
}
void ImGuiAddon::renderFrame(vk::CommandBuffer cmdBuffer)
{
for (const std::unique_ptr<ImGuiWidget>& widget : mWidgets) {
widget->draw();
}
ImGui::Render();
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), cmdBuffer);
beginFrame();
}
void ImGuiAddon::removeWidget(ImGuiWidget* widget)
{
auto it = std::ranges::find_if(mWidgets, [widget](const std::unique_ptr<ImGuiWidget>& widgetPtr)
{
return widgetPtr.get() == widget;
});
mWidgets.erase(it);
}
void ImGuiAddon::beginFrame() noexcept
{
ImGui_ImplVulkan_NewFrame();
ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame();
}
void ImGuiAddon::handleKeyChanged(const KeyEvent& event)
{
ImGui::GetIO().AddKeyEvent(keyToImGui(event.keyCode), event.down);
}
void ImGuiAddon::handleMouseButtonChanged(const MouseButtonEvent& event)
{
ImGui::GetIO().AddMouseButtonEvent(mouseButtonToImGui(event.button), event.down);
}
void ImGuiAddon::handleMouseMoved(const MouseMoveEvent& event)
{
ImGui::GetIO().AddMousePosEvent(static_cast<float>(event.absoluteX), static_cast<float>(event.absoluteY));
}
void ImGuiAddon::handleMouseScrolled(const MouseWheelEvent& event)
{
ImGui::GetIO().AddMouseWheelEvent(static_cast<float>(event.relativeX), static_cast<float>(event.relativeY));
}
void ImGuiAddon::handleTextEntered(const TextInputEvent& event)
{
ImGui::GetIO().AddInputCharactersUTF8(event.text.c_str());
}
ImGuiAddon& ImGuiAddon::get() noexcept
{
return gImguiAddon;
}
} // namespace iwa

View File

@@ -0,0 +1,21 @@
#include "iwa/addons/imgui/fps_widget.hpp"
#include <fmt/format.h>
#include <imgui.h>
namespace iwa
{
void ImGuiFpsWidget::draw()
{
mFpsCalculator.tickFrame();
const std::string fpsText = fmt::format("{}", static_cast<unsigned>(mFpsCalculator.getFps()));
const ImVec2 textSize = ImGui::CalcTextSize(fpsText.c_str());
ImGui::SetNextWindowPos(ImVec2(0, 0));
ImGui::SetNextWindowSize(ImVec2(textSize.x + 2 * ImGui::GetStyle().WindowPadding.x, 0));
ImGui::Begin("#fps", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs);
ImGui::TextUnformatted(fpsText.c_str());
ImGui::End();
}
} // namespace iwa

View File

@@ -0,0 +1,37 @@
#include "iwa/app/vulkan_application.hpp"
#include <mijin/virtual_filesystem/filesystem.hpp>
#include <mijin/virtual_filesystem/relative.hpp>
namespace iwa
{
VulkanApplication::VulkanApplication(const ApplicationCreationArgs& args, ObjectPtr<> owner) : super_t(std::move(owner))
{
mInstance = Instance::create(args.instanceArgs);
mDevice = mInstance->createDevice(args.deviceArgs);
mMainWindow = mInstance->createWindow(args.mainWindowArgs);
SwapchainCreationArgs swapchainCreationArgs = args.mainWindowSwapchainArgs;
MIJIN_ASSERT(swapchainCreationArgs.window == nullptr, "Main window swapchain args shouldn't contain a window.");
swapchainCreationArgs.window = mMainWindow;
mMainWindowSwapchain = mDevice->createChild<Swapchain>(swapchainCreationArgs);
if (!args.assetPath.empty())
{
mInstance->getPrimaryFSAdapter().emplaceAdapter<mijin::RelativeFileSystemAdapter<mijin::OSFileSystemAdapter>>(
/* root = */ fs::current_path() / "assets"
);
}
}
int VulkanApplication::execute(int argc, char** argv) // NOLINT
{
(void) argc;
(void) argv;
mInstance->getMainTaskLoop().addTask(c_init());
mInstance->getMainTaskLoop().runUntilDone();
return 0;
}
} // namespace iwa

101
source/buffer.cpp Normal file
View File

@@ -0,0 +1,101 @@
#include "iwa/buffer.hpp"
#include "iwa/device.hpp"
namespace iwa
{
Buffer::Buffer(ObjectPtr<Device> owner, const BufferCreationArgs& args) : super_t(std::move(owner))
{
mHandle = getOwner()->getVkHandle().createBuffer(vk::BufferCreateInfo
{
.flags = args.flags,
.size = args.size,
.usage = args.usage,
.sharingMode = args.sharingMode,
.queueFamilyIndexCount = static_cast<std::uint32_t>(args.queueFamilyIndices.size()),
.pQueueFamilyIndices = args.queueFamilyIndices.data()
});
}
Buffer::~Buffer() noexcept
{
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyBuffer);
}
void Buffer::allocateMemory(HostVisible hostVisible, HostCoherent hostCoherent)
{
const vk::MemoryRequirements memoryRequirements = getOwner()->getVkHandle().getBufferMemoryRequirements(mHandle);
const vk::MemoryPropertyFlags memoryFlags = hostVisible ?
(vk::MemoryPropertyFlagBits::eHostVisible | (hostCoherent ? vk::MemoryPropertyFlagBits::eHostCoherent : vk::MemoryPropertyFlags())) :
vk::MemoryPropertyFlagBits::eDeviceLocal;
const std::optional<std::uint32_t> memoryTypeIdx = findMemoryType(*getOwner(), memoryRequirements, memoryFlags);
if (!memoryTypeIdx.has_value())
{
throw std::runtime_error("Could not find a suitable memory type.");
}
ObjectPtr<DeviceMemory> memory = getOwner()->allocateDeviceMemory(
{
.allocationSize = memoryRequirements.size,
.memoryTypeIndex = memoryTypeIdx.value()
});
bindMemory(std::move(memory));
}
void Buffer::bindMemory(ObjectPtr<DeviceMemory> memory, vk::DeviceSize offset)
{
mMemory = std::move(memory);
getOwner()->getVkHandle().bindBufferMemory(mHandle, *mMemory, offset);
}
mijin::Task<> Buffer::c_fill(std::uint32_t data, std::size_t bytes, std::size_t byteOffset)
{
ObjectPtr<CommandBuffer> cmdBufferPtr = getOwner()->beginScratchCommandBuffer();
const vk::CommandBuffer cmdBuffer = *cmdBufferPtr;
cmdBuffer.fillBuffer(mHandle, byteOffset, bytes, data);
co_await getOwner()->endScratchCommandBuffer(std::move(cmdBufferPtr));
}
mijin::Task<> Buffer::c_copyFrom(vk::Buffer srcBuffer, vk::BufferCopy region)
{
ObjectPtr<CommandBuffer> cmdBufferPtr = getOwner()->beginScratchCommandBuffer();
const vk::CommandBuffer cmdBuffer = *cmdBufferPtr;
cmdBuffer.copyBuffer(srcBuffer, mHandle, region);
co_await getOwner()->endScratchCommandBuffer(std::move(cmdBufferPtr));
}
mijin::Task<> Buffer::c_upload(const void* data, std::size_t bytes, std::size_t byteOffset)
{
// assert(bytes == SIZE_REST || bytes + byteOffset <= byteSize);
// if (bytes == SIZE_REST) {
// bytes = byteSize - byteOffset;
// }
// create scratch buffer
vk::Device device = *getOwner();
ObjectPtr<Buffer> scratchBuffer = getOwner()->createChild<Buffer>(BufferCreationArgs{
.size = bytes,
.usage = vk::BufferUsageFlagBits::eTransferSrc
});
scratchBuffer->allocateMemory(HostVisible::YES);
// copy to scratch buffer
void* mapped = device.mapMemory(*scratchBuffer->getMemory(), 0, bytes);
std::memcpy(mapped, data, bytes);
device.unmapMemory(*scratchBuffer->getMemory());
// copy to actual buffer
co_await c_copyFrom(*scratchBuffer, vk::BufferCopy{
.srcOffset = 0,
.dstOffset = byteOffset,
.size = bytes
});
}
mijin::Task<> Buffer::c_upload(const mijin::TypelessBuffer& data, std::size_t byteOffset)
{
return c_upload(data.data(), data.byteSize(), byteOffset);
}
} // namespace iwa

65
source/command.cpp Normal file
View File

@@ -0,0 +1,65 @@
#include "iwa/command.hpp"
#include "iwa/device.hpp"
namespace iwa
{
CommandPool::CommandPool(ObjectPtr<Device> owner, CommandPoolCreationArgs args) : super_t(std::move(owner))
{
mHandle = getOwner()->getVkHandle().createCommandPool(vk::CommandPoolCreateInfo{
.flags = args.flags,
.queueFamilyIndex = args.queueFamilyIndex
});
}
CommandPool::~CommandPool() noexcept
{
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyCommandPool);
}
ObjectPtr<CommandBuffer> CommandPool::allocateCommandBuffer(const CommandBufferAllocateArgs& args)
{
vk::CommandBuffer commandBuffer;
const vk::CommandBufferAllocateInfo allocateInfo
{
.commandPool = mHandle,
.level = args.level,
.commandBufferCount = 1
};
vk::resultCheck(getOwner()->getVkHandle().allocateCommandBuffers(&allocateInfo, &commandBuffer),
"vkAllocateCommandBuffers failed");
return createChild<CommandBuffer>(commandBuffer);
}
// std::vector<vk::CommandBuffer> CommandPool::allocateCommandBuffers(
// std::size_t count,
// const CommandBufferAllocateArgs& args) const noexcept
// {
// return getOwner()->getVkHandle().allocateCommandBuffers(vk::CommandBufferAllocateInfo{
// .commandPool = mHandle,
// .level = args.level,
// .commandBufferCount = static_cast<std::uint32_t>(count)
// });
// }
CommandBuffer::CommandBuffer(ObjectPtr<iwa::CommandPool> owner, vk::CommandBuffer handle)
: super_t(std::move(owner)), MixinVulkanObject(handle)
{
}
CommandBuffer::~CommandBuffer() noexcept
{
if (mHandle)
{
getOwner()->getOwner()->queueDelete([
poolHandle = getOwner()->getVkHandle(),
handle = mHandle,
device = getOwner()->getOwner()->getVkHandle()]
{
device.freeCommandBuffers(poolHandle, handle);
});
}
}
} // namespace iwa

90
source/descriptor_set.cpp Normal file
View File

@@ -0,0 +1,90 @@
#include "iwa/descriptor_set.hpp"
#include "iwa/device.hpp"
namespace iwa
{
DescriptorSetLayout::DescriptorSetLayout(ObjectPtr<Device> owner, const DescriptorSetLayoutCreationArgs& args)
: super_t(std::move(owner))
{
vk::DescriptorSetLayoutCreateInfo createInfo{
.flags = args.flags,
.bindingCount = static_cast<std::uint32_t>(args.bindings.size()),
.pBindings = args.bindings.data()
};
vk::DescriptorSetLayoutBindingFlagsCreateInfo flagsInfo;
if (!args.bindingFlags.empty())
{
MIJIN_ASSERT(args.bindings.size() == args.bindingFlags.size(), "Binding flags must be empty or same size as bindings.");
flagsInfo.bindingCount = static_cast<std::uint32_t>(args.bindingFlags.size()),
flagsInfo.pBindingFlags = args.bindingFlags.data();
createInfo.pNext = &flagsInfo;
}
mHandle = getOwner()->getVkHandle().createDescriptorSetLayout(createInfo);
}
DescriptorSetLayout::~DescriptorSetLayout() noexcept
{
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyDescriptorSetLayout)
}
DescriptorPool::DescriptorPool(ObjectPtr<Device> owner, const DescriptorPoolCreationArgs& args)
: super_t(std::move(owner)), mCanFree(args.flags & vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet)
{
mHandle = getOwner()->getVkHandle().createDescriptorPool(vk::DescriptorPoolCreateInfo
{
.flags = args.flags,
.maxSets = args.maxSets,
.poolSizeCount = static_cast<std::uint32_t>(args.poolSizes.size()),
.pPoolSizes = args.poolSizes.data()
});
}
DescriptorPool::~DescriptorPool() noexcept
{
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyDescriptorPool);
}
ObjectPtr<DescriptorSet> DescriptorPool::allocateDescriptorSet(const DescriptorSetAllocateArgs& args)
{
vk::DescriptorSet descriptorSet;
vk::DescriptorSetAllocateInfo allocateInfo
{
.descriptorPool = mHandle,
.descriptorSetCount = 1,
.pSetLayouts = &args.layout->getVkHandle(),
};
vk::DescriptorSetVariableDescriptorCountAllocateInfo variableSetInfo;
if (args.variableDescriptorCount > 0)
{
variableSetInfo.descriptorSetCount = 1;
variableSetInfo.pDescriptorCounts = &args.variableDescriptorCount;
allocateInfo.pNext = &variableSetInfo;
}
vk::resultCheck(getOwner()->getVkHandle().allocateDescriptorSets(&allocateInfo, &descriptorSet),
"vkAllocateDescriptorSets failed");
return createChild<DescriptorSet>(descriptorSet);
}
DescriptorSet::DescriptorSet(ObjectPtr<DescriptorPool> owner, vk::DescriptorSet handle)
: super_t(std::move(owner)), MixinVulkanObject(handle)
{
}
DescriptorSet::~DescriptorSet() noexcept
{
if (mHandle && getOwner()->getCanFree())
{
getOwner()->getOwner()->queueDelete([
poolHandle = getOwner()->getVkHandle(),
handle = mHandle,
device = getOwner()->getOwner()]
{
device->getVkHandle().freeDescriptorSets(poolHandle, handle);
});
}
}
} // namespace iwa

381
source/device.cpp Normal file
View File

@@ -0,0 +1,381 @@
#include "iwa/device.hpp"
#include "iwa/log.hpp"
#include "iwa/instance.hpp"
namespace iwa
{
namespace
{
void buildDefaultDeviceExtensionList(std::vector<ExtensionInfo>& outExtensions) noexcept
{
outExtensions.push_back({.name = VK_KHR_SWAPCHAIN_EXTENSION_NAME, .required = true});
}
bool checkQueueFamilies(const PhysicalDeviceInfo& deviceInfo, [[maybe_unused]] int& score)
{
return deviceInfo.graphicsQueueFamily != std::numeric_limits<std::uint32_t>::max()
&& deviceInfo.computeQueueFamily != std::numeric_limits<std::uint32_t>::max();
}
bool checkDeviceExtensions(const std::vector<ExtensionInfo>& extensions, const PhysicalDeviceInfo& deviceInfo, int& score)
{
auto isExtensionSupported = [&deviceInfo](const char* extension)
{
for (const vk::ExtensionProperties& props : deviceInfo.extensions)
{
if (std::strncmp(props.extensionName, extension, VK_MAX_EXTENSION_NAME_SIZE) == 0)
{
return true;
}
}
return false;
};
for (const ExtensionInfo& extInfo : extensions)
{
if (isExtensionSupported(extInfo.name))
{
score += 10;
}
else if (extInfo.required)
{
logVerbose("Vulkan device {} not supported as it is missing required extension {}.", deviceInfo.properties.deviceName, extInfo.name);
return false;
}
}
return true;
}
int scorePhysicalDevice(
const PhysicalDeviceCriteria& deviceCriteria,
const std::vector<ExtensionInfo>& extensions,
const PhysicalDeviceInfo& deviceInfo)
{
int score = 0;
switch (deviceInfo.properties.deviceType)
{
case vk::PhysicalDeviceType::eDiscreteGpu:
score = 1000;
break;
case vk::PhysicalDeviceType::eIntegratedGpu:
score = 100;
break;
default: break;
}
#define CHECK_FEATURE_STRUCT(infoName, requiredName) \
for (auto feature : deviceCriteria.requiredName) \
{ \
if (!(deviceInfo.infoName.*feature)) \
{ \
return -1; \
} \
}
CHECK_FEATURE_STRUCT(features, requiredFeatures)
CHECK_FEATURE_STRUCT(vulkan11Features, requiredVulkan11Features)
CHECK_FEATURE_STRUCT(vulkan12Features, requiredVulkan12Features)
CHECK_FEATURE_STRUCT(vulkan13Features, requiredVulkan13Features)
CHECK_FEATURE_STRUCT(accelerationStructureFeatures, requiredAccelerationStructureFeatures)
CHECK_FEATURE_STRUCT(rayTracingPipelineFeatures, requiredRayTracingPipelineFeatures)
CHECK_FEATURE_STRUCT(meshShaderFeatures, requredMeshShaderFeatures)
#undef CHECK_FEATURE_STRUCT
if (!checkQueueFamilies(deviceInfo, score))
{
logVerbose("Vulkan device {} not supported as no suitable queue family configuration could be found.", deviceInfo.properties.deviceName);
return -1;
}
if (!checkDeviceExtensions(extensions, deviceInfo, score))
{
return -1;
}
return score;
}
std::size_t findBestPhysicalDevice(
PhysicalDeviceCriteria& deviceCriteria,
const std::vector<ExtensionInfo>& extensions,
const std::vector<PhysicalDeviceInfo>& deviceInfos)
{
int bestScore = -1;
std::size_t bestIdx = 0;
for (std::size_t idx = 0; idx < deviceInfos.size(); ++idx)
{
const int score = scorePhysicalDevice(deviceCriteria, extensions, deviceInfos[idx]);
if (score > bestScore)
{
bestScore = score;
bestIdx = idx;
}
}
if (bestScore < 0)
{
logAndDie("Could not find a suitable Vulkan device!");
}
return bestIdx;
}
std::vector<const char*> buildDeviceExtensionNameList(const PhysicalDeviceInfo& deviceInfo, std::vector<ExtensionInfo>& extensions)
{
auto isExtensionSupported = [&](const char* extension)
{
for (const vk::ExtensionProperties& props : deviceInfo.extensions)
{
if (std::strncmp(props.extensionName, extension, VK_MAX_EXTENSION_NAME_SIZE) == 0)
{
return true;
}
}
return false;
};
std::vector<const char*> enabledExtensions;
for (ExtensionInfo& extInfo : extensions)
{
const bool supported = isExtensionSupported(extInfo.name);
if (!supported && extInfo.required)
{
// this shouldn't be possible, we already checked when scoring the device
logAndDie("Required Vulkan device extension not supported: {}.", extInfo.name);
}
if (supported) {
enabledExtensions.push_back(extInfo.name);
}
extInfo.enabled = supported;
}
return enabledExtensions;
}
std::vector<vk::DeviceQueueCreateInfo> buildQueueCreateInfoList(const PhysicalDeviceInfo& deviceInfo)
{
static const float QUEUE_PRIORITY_ONE = 1.0f;
std::vector<vk::DeviceQueueCreateInfo> createInfos;
createInfos.emplace_back()
.setQueueFamilyIndex(deviceInfo.graphicsQueueFamily)
.setQueueCount(1)
.setPQueuePriorities(&QUEUE_PRIORITY_ONE);
return createInfos;
}
} // namespace
Device::Device(ObjectPtr<Instance> owner, DeviceCreationArgs args)
: super_t(std::move(owner)),
mExtensions(std::move(args.extensions))
{
if (!args.flags.noDefaultExtensions)
{
buildDefaultDeviceExtensionList(mExtensions);
}
const std::vector<PhysicalDeviceInfo>& physicalDevices = getOwner()->getPhysicalDevices();
const std::size_t physicalDeviceIdx = findBestPhysicalDevice(args.physicalDeviceCriteria, mExtensions, physicalDevices);
mDeviceInfo = &physicalDevices[physicalDeviceIdx];
const std::vector<const char*> enabledExtensions = buildDeviceExtensionNameList(*mDeviceInfo, mExtensions);
const std::vector<vk::DeviceQueueCreateInfo> queueCreateInfos = buildQueueCreateInfoList(*mDeviceInfo);
void* pNext = nullptr;
#define APPEND_FEATURE_STRUCT(type, name, requiredArray) \
type name{.pNext = pNext}; \
if (!args.physicalDeviceCriteria.requiredArray.empty()) \
{ \
for (auto feature : args.physicalDeviceCriteria.requiredArray) \
{ \
(name).*(feature) = VK_TRUE; \
} \
pNext = &(name); \
}
APPEND_FEATURE_STRUCT(vk::PhysicalDeviceVulkan11Features, vulkan11Features, requiredVulkan11Features)
APPEND_FEATURE_STRUCT(vk::PhysicalDeviceVulkan12Features, vulkan12Features, requiredVulkan12Features)
APPEND_FEATURE_STRUCT(vk::PhysicalDeviceVulkan13Features, vulkan13Features, requiredVulkan13Features)
APPEND_FEATURE_STRUCT(vk::PhysicalDeviceAccelerationStructureFeaturesKHR, accelerationStructureFeatures, requiredAccelerationStructureFeatures)
APPEND_FEATURE_STRUCT(vk::PhysicalDeviceRayTracingPipelineFeaturesKHR, rayTracingPipelineFeature, requiredRayTracingPipelineFeatures)
APPEND_FEATURE_STRUCT(vk::PhysicalDeviceMeshShaderFeaturesEXT, meshShaderFeatures, requredMeshShaderFeatures)
#undef APPEND_FEATURE_STRUCT
vk::PhysicalDeviceFeatures enabledFeatures; // NOLINT(*-const-correctness) false positive
for (auto feature : args.physicalDeviceCriteria.requiredFeatures)
{
enabledFeatures.*feature = VK_TRUE;
}
const vk::DeviceCreateInfo deviceCreateInfo =
{
.pNext = pNext,
.queueCreateInfoCount = static_cast<std::uint32_t>(queueCreateInfos.size()),
.pQueueCreateInfos = queueCreateInfos.data(),
.enabledExtensionCount = static_cast<std::uint32_t>(enabledExtensions.size()),
.ppEnabledExtensionNames = enabledExtensions.data(),
.pEnabledFeatures = &enabledFeatures
};
mHandle = mDeviceInfo->device.createDevice(deviceCreateInfo);
if (args.flags.singleDevice)
{
VULKAN_HPP_DEFAULT_DISPATCHER.init(mHandle);
}
mGraphicsQueue = mHandle.getQueue(mDeviceInfo->graphicsQueueFamily, 0);
if (mDeviceInfo->graphicsQueueFamily == mDeviceInfo->computeQueueFamily)
{
mComputeQueue = mGraphicsQueue;
}
else
{
mComputeQueue = mHandle.getQueue(mDeviceInfo->computeQueueFamily, 0);
}
getOwner()->getMainTaskLoop().addTask(c_updateLoop(), &mUpdateLoopHandle);
getOwner()->deviceCreated.emit(*this);
}
Device::~Device() noexcept
{
mUpdateLoopHandle.cancel();
mScratchCommandPools.clear(); // hack2: due to the below hack the pools would get destroyed after the device, reset them manually
if (mHandle)
{
mHandle.waitIdle();
mPendingScratchCmdBuffers.clear();
getOwner()->queueDelete([handle=mHandle]()
{
handle.destroy();
});
}
}
ScratchCommandPool::ScratchCommandPool(Device& device)
{
mCommandPool = device.createChild<CommandPool>(CommandPoolCreationArgs{
.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
.queueFamilyIndex = device.getDeviceInfo().graphicsQueueFamily
});
}
ObjectPtr<CommandBuffer> ScratchCommandPool::allocateCommandBuffer()
{
for (Buffer& buffer : mBuffers)
{
if (buffer.doneFuture->ready())
{
buffer.doneFuture = std::make_shared<mijin::Future<void>>();
buffer.cmdBuffer->getVkHandle().reset();
return buffer.cmdBuffer;
}
}
// nothing found, allocate a new one
return mBuffers.emplace_back(mCommandPool->allocateCommandBuffer(), std::make_shared<mijin::Future<void>>()).cmdBuffer;
}
mijin::FuturePtr<void> ScratchCommandPool::getFuture(const ObjectPtr<CommandBuffer>& cmdBuffer) noexcept
{
for (const Buffer& buffer : mBuffers)
{
if (buffer.cmdBuffer == cmdBuffer)
{
return buffer.doneFuture;
}
}
logAndDie("Someone passed an invalid cmdBuffer to getFuture()!");
}
ObjectPtr<DeviceMemory> Device::allocateDeviceMemory(const DeviceMemoryAllocationArgs& args)
{
return createChild<DeviceMemory>(args);
}
ObjectPtr<CommandBuffer> Device::beginScratchCommandBuffer()
{
ObjectPtr<CommandBuffer> cmdBuffer;
{
const std::shared_lock readLock(mScratchCommandPoolsMutex);
auto it = mScratchCommandPools.find(std::this_thread::get_id());
if (it != mScratchCommandPools.end())
{
cmdBuffer = it->second.allocateCommandBuffer();
}
}
if (cmdBuffer == nullptr)
{
// create the scratch command pool
const std::unique_lock writeLock(mScratchCommandPoolsMutex);
ScratchCommandPool& pool = mScratchCommandPools.emplace(std::this_thread::get_id(), ScratchCommandPool(*this)).first->second;
cmdBuffer = pool.allocateCommandBuffer();
// hack: an object should normally not contain pointers to its own child as that leads to cyclic dependencies
// decrease reference count by 1 to migitate that
decreaseReferenceCount();
}
const vk::CommandBufferBeginInfo beginInfo =
{
.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit
};
cmdBuffer->getVkHandle().begin(beginInfo);
return cmdBuffer;
}
mijin::FuturePtr<void> Device::endScratchCommandBuffer(ObjectPtr<CommandBuffer> cmdBuffer)
{
mijin::FuturePtr<void> future;
{
const std::shared_lock readLock(mScratchCommandPoolsMutex);
future = mScratchCommandPools.at(std::this_thread::get_id()).getFuture(cmdBuffer);
}
cmdBuffer->getVkHandle().end();
getOwner()->runOnMainThread([cmdBuffer = std::move(cmdBuffer), self = getPointer(), future]() mutable
{
ObjectPtr<Fence> doneFence = Fence::create(self);
const vk::SubmitInfo submitInfo =
{
.commandBufferCount = 1,
.pCommandBuffers = &cmdBuffer->getVkHandle()
};
self->getGraphicsQueue().submit(submitInfo, *doneFence);
self->mPendingScratchCmdBuffers.push_back({
.cmdBuffer = std::move(cmdBuffer),
.doneFence = std::move(doneFence),
.future = std::move(future)
});
});
return future;
}
void Device::queueDelete(std::function<void()> deleter) noexcept
{
getOwner()->queueDelete(std::move(deleter));
}
mijin::Task<> Device::c_updateLoop() noexcept
{
while(!getOwner()->isQuitRequested())
{
for (auto it = mPendingScratchCmdBuffers.begin(); it != mPendingScratchCmdBuffers.end();)
{
if (it->doneFence->isDone())
{
it->future->set();
it = mPendingScratchCmdBuffers.erase(it);
} else
{
++it;
}
}
co_await mijin::c_suspend();
}
}
}

39
source/device_memory.cpp Normal file
View File

@@ -0,0 +1,39 @@
#include "iwa/device_memory.hpp"
#include "iwa/device.hpp"
namespace iwa
{
DeviceMemory::DeviceMemory(ObjectPtr<Device> owner, const DeviceMemoryAllocationArgs& args) : super_t(std::move(owner))
{
mHandle = getOwner()->getVkHandle().allocateMemory(vk::MemoryAllocateInfo
{
.allocationSize = args.allocationSize,
.memoryTypeIndex = args.memoryTypeIndex
});
}
DeviceMemory::~DeviceMemory() noexcept
{
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, freeMemory);
}
std::optional<std::uint32_t> findMemoryType(Device& device, const vk::MemoryRequirements& requirements, vk::MemoryPropertyFlags properties)
{
const vk::PhysicalDeviceMemoryProperties& memoryProperties = device.getDeviceInfo().memoryProperties;
for (std::uint32_t idx = 0; idx < memoryProperties.memoryTypeCount; ++idx)
{
if ((requirements.memoryTypeBits & (1 << idx)) == 0) {
continue; // not suitable for this buffer
}
if ((memoryProperties.memoryTypes[idx].propertyFlags & properties) != properties) {
continue; // does not fulfill required properties
}
return idx;
}
return std::nullopt;
}
} // namespace iwa

28
source/event.cpp Normal file
View File

@@ -0,0 +1,28 @@
#include "iwa/event.hpp"
#include "iwa/device.hpp"
namespace iwa
{
Event::Event(ObjectPtr<Device> owner, const EventCreationArgs& args) : super_t(std::move(owner))
{
mHandle = getOwner()->getVkHandle().createEvent(vk::EventCreateInfo
{
.flags = args.flags
});
}
Event::~Event() noexcept
{
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyEvent);
}
mijin::Task<> Event::c_wait()
{
while (getOwner()->getVkHandle().getEventStatus(mHandle) != vk::Result::eEventSet)
{
co_await mijin::c_suspend();
}
}
}

38
source/fence.cpp Normal file
View File

@@ -0,0 +1,38 @@
#include "iwa/fence.hpp"
#include "iwa/device.hpp"
namespace iwa
{
Fence::Fence(ObjectPtr<Device> owner, const FenceCreationArgs& args) : super_t(std::move(owner))
{
mHandle = getOwner()->getVkHandle().createFence(vk::FenceCreateInfo{
.flags = args.flags
});
}
Fence::~Fence() noexcept
{
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyFence)
}
bool Fence::isDone() const
{
return getOwner()->getVkHandle().waitForFences(mHandle, VK_TRUE, 0) != vk::Result::eTimeout;
}
mijin::Task<> Fence::c_wait() const
{
while (getOwner()->getVkHandle().waitForFences(mHandle, VK_TRUE, 0) == vk::Result::eTimeout)
{
co_await mijin::c_suspend();
}
co_return;
}
void Fence::reset() const
{
getOwner()->getVkHandle().resetFences(mHandle);
}
} // namespace iwa

384
source/image.cpp Normal file
View File

@@ -0,0 +1,384 @@
#include "iwa/image.hpp"
#include <cmath>
#include "iwa/resource/bitmap.hpp"
#include "iwa/buffer.hpp"
#include "iwa/device.hpp"
#include "iwa/util/vkutil.hpp"
namespace iwa
{
Image::Image(ObjectPtr<Device> owner, ImageCreationArgs args) : super_t(std::move(owner)), mFlags(args.flags), mType(args.imageType),
mFormat(args.format), mTiling(args.tiling), mUsage(args.usage), mSize(args.extent), mArrayLayers(args.arrayLayers),
mMipLevels(clampMipLevels(args.mipLevels))
{
if (mMipLevels > 1) {
mUsage |= vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eTransferSrc;
}
mHandle = getOwner()->getVkHandle().createImage(vk::ImageCreateInfo{
.flags = args.flags,
.imageType = args.imageType,
.format = args.format,
.extent = args.extent,
.mipLevels = mMipLevels,
.arrayLayers = args.arrayLayers,
.samples = args.samples,
.tiling = args.tiling,
.usage = mUsage,
.sharingMode = args.sharingMode,
.queueFamilyIndexCount = static_cast<std::uint32_t>(args.queueFamilyIndices.size()),
.pQueueFamilyIndices = args.queueFamilyIndices.data(),
.initialLayout = args.initialLayout
});
}
Image::Image(ObjectPtr<Device> owner, ImageWrapArgs args)
: super_t(std::move(owner)), MixinVulkanObject(args.handle), mType(args.type), mFormat(args.format), mUsage(args.usage),
mSize(args.size), mMipLevels(args.mipLevels), mWrapped(true)
{
}
Image::~Image() noexcept
{
if (!mWrapped)
{
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyImage)
}
}
void Image::resetUsage(ResetLayout resetLayout) noexcept
{
lastUsageStages = vk::PipelineStageFlagBits::eTopOfPipe;
lastAccess = vk::AccessFlags();
if (resetLayout)
{
currentLayout = vk::ImageLayout::eUndefined;
}
}
void Image::allocateMemory()
{
const vk::MemoryRequirements memoryRequirements = getOwner()->getVkHandle().getImageMemoryRequirements(mHandle);
const vk::MemoryPropertyFlags memoryFlags = vk::MemoryPropertyFlagBits::eDeviceLocal;
const std::optional<std::uint32_t> memoryTypeIdx = findMemoryType(*getOwner(), memoryRequirements, memoryFlags);
if (!memoryTypeIdx.has_value())
{
throw std::runtime_error("Could not find a suitable memory type.");
}
ObjectPtr<DeviceMemory> memory = getOwner()->allocateDeviceMemory(
{
.allocationSize = memoryRequirements.size,
.memoryTypeIndex = memoryTypeIdx.value()
});
bindMemory(std::move(memory));
}
void Image::bindMemory(ObjectPtr<DeviceMemory> memory, vk::DeviceSize offset)
{
mMemory = std::move(memory);
getOwner()->getVkHandle().bindImageMemory(mHandle, *mMemory, offset);
}
void Image::applyTransition(vk::CommandBuffer cmdBuffer, const ImageTransition& transition)
{
assert(transition.layout != vk::ImageLayout::eUndefined);
if (transition.layout != currentLayout)
{
cmdBuffer.pipelineBarrier(
/* srcStageMask = */ lastUsageStages,
/* dstStageMask = */ transition.stages,
/* dependencyFlags = */ vk::DependencyFlags(),
/* memoryBarriers = */ {},
/* bufferMemoryBarriers = */ {},
/* imageMemoryBarriers = */ {
vk::ImageMemoryBarrier{
.srcAccessMask = lastAccess,
.dstAccessMask = transition.access,
.oldLayout = currentLayout,
.newLayout = transition.layout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = mHandle,
.subresourceRange = transition.subResourceRange
}
});
}
lastUsageStages = transition.stages;
lastAccess = transition.access;
currentLayout = transition.layout;
}
ObjectPtr<ImageView> Image::createImageView(const ImageViewCreationArgs& args)
{
return createChild<ImageView>(args);
}
mijin::Task<> Image::c_upload(const void* data, std::size_t bytes, vk::Extent3D bufferImageSize, vk::Offset3D imageOffset,
unsigned baseLayer, unsigned layerCount)
{
MIJIN_ASSERT(bytes >= static_cast<std::size_t>(bufferImageSize.width) * bufferImageSize.height * bufferImageSize.depth
* vkFormatSize(mFormat), "Buffer for image upload is too small!");
// TODO: optimize this whole process
// create scratch buffer
vk::Device device = *getOwner();
ObjectPtr<Buffer> scratchBuffer = getOwner()->createChild<Buffer>(BufferCreationArgs{
.size = bytes,
.usage = vk::BufferUsageFlagBits::eTransferSrc
});
scratchBuffer->allocateMemory(HostVisible::YES);
// copy to scratch buffer
void* mapped = device.mapMemory(*scratchBuffer->getMemory(), 0, bytes);
std::memcpy(mapped, data, bytes);
device.unmapMemory(*scratchBuffer->getMemory());
ObjectPtr<CommandBuffer> cmdBufferPtr = getOwner()->beginScratchCommandBuffer();
const vk::CommandBuffer cmdBuffer = *cmdBufferPtr;
applyTransition(cmdBuffer, {
.stages = vk::PipelineStageFlagBits::eTransfer,
.layout = vk::ImageLayout::eTransferDstOptimal,
.access = vk::AccessFlagBits::eTransferWrite
});
// copy to actual buffer
cmdBuffer.copyBufferToImage(*scratchBuffer, mHandle, vk::ImageLayout::eTransferDstOptimal, vk::BufferImageCopy{
.bufferOffset = 0,
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource = {
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = 0,
.baseArrayLayer = baseLayer,
.layerCount = layerCount
},
.imageOffset = {
.x = imageOffset.x,
.y = imageOffset.y,
.z = imageOffset.z
},
.imageExtent = {
.width = bufferImageSize.width,
.height = bufferImageSize.height,
.depth = bufferImageSize.depth
}
});
if (mMipLevels > 1)
{
generateMipMaps(cmdBuffer);
}
co_await getOwner()->endScratchCommandBuffer(std::move(cmdBufferPtr));
}
mijin::Task<> Image::c_doTransition(const ImageTransition& transition)
{
ObjectPtr<CommandBuffer> cmdBufferPtr = getOwner()->beginScratchCommandBuffer();
const vk::CommandBuffer cmdBuffer = *cmdBufferPtr;
applyTransition(cmdBuffer, transition);
co_await getOwner()->endScratchCommandBuffer(std::move(cmdBufferPtr));
}
mijin::Task<> Image::c_upload(const Bitmap& bitmap, vk::Offset3D imageOffset, unsigned baseLayer, unsigned layerCount)
{
MIJIN_ASSERT(vkFormatSize(mFormat) == vkFormatSize(bitmap.getFormat()), "Bitmap format size doesn't match image format size.");
const vk::Extent3D bufferImageSize = {
.width = bitmap.getSize().width,
.height = bitmap.getSize().height,
.depth = 1
};
return c_upload(bitmap.getData().data(), bitmap.getData().size_bytes(), bufferImageSize, imageOffset, baseLayer, layerCount);
}
mijin::Task<> Image::c_blitFrom(Image& srcImage, std::vector<vk::ImageBlit> regions, vk::Filter filter)
{
ObjectPtr<CommandBuffer> cmdBufferPtr = getOwner()->beginScratchCommandBuffer();
const vk::CommandBuffer cmdBuffer = *cmdBufferPtr;
applyTransition(cmdBuffer, IMAGE_TRANSITION_TRANSFER_WRITE);
srcImage.applyTransition(cmdBuffer, IMAGE_TRANSITION_TRANSFER_READ);
cmdBuffer.blitImage(
/* srcImage = */ srcImage,
/* srcImageLayout = */ vk::ImageLayout::eTransferSrcOptimal,
/* dstImage = */ *this,
/* dstImageLayout = */ vk::ImageLayout::eTransferDstOptimal,
/* regions = */ regions,
/* filter = */ filter
);
co_await getOwner()->endScratchCommandBuffer(std::move(cmdBufferPtr));
}
mijin::Task<> Image::c_blitFrom(const Bitmap& bitmap, std::vector<vk::ImageBlit> regions, vk::Filter filter)
{
ObjectPtr<Image> scratchImage = co_await c_create(getOwner()->getPointer(), ImageFromBitmapArgs{
.bitmap = &bitmap,
.usage = vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eTransferSrc
});
co_await c_blitFrom(*scratchImage, std::move(regions), filter);
}
mijin::Task<> Image::c_copyFrom(Image& srcImage, std::vector<vk::ImageCopy> regions)
{
ObjectPtr<CommandBuffer> cmdBufferPtr = getOwner()->beginScratchCommandBuffer();
const vk::CommandBuffer cmdBuffer = *cmdBufferPtr;
applyTransition(cmdBuffer, IMAGE_TRANSITION_TRANSFER_WRITE);
srcImage.applyTransition(cmdBuffer, IMAGE_TRANSITION_TRANSFER_READ);
cmdBuffer.copyImage(
/* srcImage = */ srcImage,
/* srcImageLayout = */ vk::ImageLayout::eTransferSrcOptimal,
/* dstImage = */ *this,
/* dstImageLayout = */ vk::ImageLayout::eTransferDstOptimal,
/* regions = */ regions
);
co_await getOwner()->endScratchCommandBuffer(std::move(cmdBufferPtr));
}
std::uint32_t Image::clampMipLevels(std::uint32_t levels) const
{
if (levels <= 1) {
return 1;
}
const vk::ImageFormatProperties props = getOwner()->getVkPhysicalDevice().getImageFormatProperties(mFormat, mType, mTiling, mUsage, mFlags);
return std::min(levels, std::min(props.maxMipLevels, static_cast<std::uint32_t>(std::log2(std::max(mSize.width, mSize.height)))+1));
}
void Image::generateMipMaps(vk::CommandBuffer cmdBuffer)
{
std::vector<vk::ImageBlit> regions;
regions.resize(mMipLevels - 1);
applyTransition(cmdBuffer, {
.stages = vk::PipelineStageFlagBits::eTransfer,
.layout = vk::ImageLayout::eGeneral, // TODO: using transfer dst/src optimal would be better, but I don't want to deal with different layout in the mips
.access = vk::AccessFlagBits::eTransferWrite | vk::AccessFlagBits::eTransferRead
});
unsigned mipWidth = mSize.width / 2;
unsigned mipHeight = mSize.height / 2;
for (unsigned level = 1; level < mMipLevels; ++level)
{
vk::ImageBlit& region = regions[level - 1];
region.srcSubresource = {
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1
};
region.srcOffsets[0].x = 0;
region.srcOffsets[0].y = 0;
region.srcOffsets[0].z = 0;
region.srcOffsets[1].x = static_cast<std::int32_t>(mSize.width);
region.srcOffsets[1].y = static_cast<std::int32_t>(mSize.height);
region.srcOffsets[1].z = 1;
region.dstSubresource = {
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = level,
.baseArrayLayer = 0,
.layerCount = 1
};
region.dstOffsets[0].x = 0;
region.dstOffsets[0].y = 0;
region.dstOffsets[0].z = 0;
region.dstOffsets[1].x = static_cast<std::int32_t>(mipWidth);
region.dstOffsets[1].y = static_cast<std::int32_t>(mipHeight);
region.dstOffsets[1].z = 1;
if (mipWidth > 1) {
mipWidth /= 2;
}
if (mipHeight > 1) {
mipHeight /= 2;
}
}
cmdBuffer.blitImage(
/* srcImage = */ mHandle,
/* srcImageLayout = */ vk::ImageLayout::eGeneral,
/* dstImage = */ mHandle,
/* dstImageLayout = */ vk::ImageLayout::eGeneral,
/* regions = */ regions,
/* filter = */ vk::Filter::eLinear
);
}
mijin::Task<ObjectPtr<Image>> Image::c_create(ObjectPtr<Device> owner, ImageFromBitmapArgs args)
{
ObjectPtr<Image> image = owner->createChild<Image>(ImageCreationArgs{
.flags = args.flags,
.imageType = vk::ImageType::e2D,
.format = args.bitmap->getFormat(),
.extent = {
.width = args.bitmap->getSize().width,
.height = args.bitmap->getSize().height,
.depth = 1
},
.mipLevels = 1,
.arrayLayers = 1,
.samples = vk::SampleCountFlagBits::e1,
.tiling = args.tiling,
.usage = args.usage | vk::ImageUsageFlagBits::eTransferDst,
.sharingMode = args.sharingMode,
.queueFamilyIndices = std::move(args.queueFamilyIndices),
.initialLayout = args.initialLayout
});
image->allocateMemory();
co_await image->c_upload(*args.bitmap);
co_return image;
}
ImageView::ImageView(ObjectPtr<Image> owner, const ImageViewCreationArgs& args) : super_t(std::move(owner))
{
mHandle = getOwner()->getOwner()->getVkHandle().createImageView(vk::ImageViewCreateInfo
{
.flags = args.flags,
.image = *getOwner(),
.viewType = args.viewType,
.format = args.format != vk::Format::eUndefined ? args.format : getOwner()->getFormat(),
.components = args.components,
.subresourceRange = args.subresourceRange
});
}
ImageView::~ImageView() noexcept
{
IWA_DELETE_DEVICE_OBJECT(getOwner()->getOwner(), mHandle, destroyImageView);
}
Sampler::Sampler(ObjectPtr<Device> owner, const SamplerCreationArgs& args) : super_t(std::move(owner))
{
mHandle = getOwner()->getVkHandle().createSampler(vk::SamplerCreateInfo
{
.flags = args.flags,
.magFilter = args.magFilter,
.minFilter = args.minFilter,
.mipmapMode = args.mipmapMode,
.addressModeU = args.addressModeU,
.addressModeV = args.addressModeV,
.addressModeW = args.addressModeW,
.mipLodBias = args.mipLodBias,
.anisotropyEnable = args.options.anisotropyEnable,
.maxAnisotropy = args.maxAnisotropy,
.compareEnable = args.options.compareEnable,
.compareOp = args.compareOp,
.minLod = args.minLod,
.maxLod = args.maxLod,
.borderColor = args.borderColor,
.unnormalizedCoordinates = args.options.unnormalizedCoordinates
});
}
Sampler::~Sampler() noexcept
{
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroySampler);
}
} // namespace iwa

37
source/input.cpp Normal file
View File

@@ -0,0 +1,37 @@
#include "iwa/input.hpp"
#include "iwa/log.hpp"
namespace iwa
{
KeyState getKeyState(ScanCode scanCode) noexcept
{
const int index = static_cast<int>(scanCode);
int numKeys = 0;
const Uint8* keys = SDL_GetKeyboardState(&numKeys);
if (index >= numKeys) {
return {};
}
return {
.pressed = keys[index] > 0
};
}
void captureMouse() noexcept
{
SDL_CaptureMouse(SDL_TRUE);
}
void uncaptureMouse() noexcept
{
SDL_CaptureMouse(SDL_FALSE);
}
std::pair<int, int> getMouseScreenPosition() noexcept
{
std::pair<int, int> position;
SDL_GetGlobalMouseState(&position.first, &position.second);
return position;
}
} // namespace iwa

462
source/instance.cpp Normal file
View File

@@ -0,0 +1,462 @@
#include "iwa/instance.hpp"
#include <mijin/detect.hpp>
#include <mijin/debug/assert.hpp>
#include "iwa/addon.hpp"
#include "iwa/log.hpp"
#include "iwa/window.hpp"
#include <vulkan/vulkan_from_string.inl>
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
namespace iwa
{
namespace
{
void buildDefaultInstanceExtensionList(std::vector<ExtensionInfo>& outExtensions) noexcept
{
#if !defined(KAZAN_RELEASE)
outExtensions.push_back({.name = VK_EXT_DEBUG_UTILS_EXTENSION_NAME, .required = false});
#endif
// if (!getEngineOptions().headless)
{
outExtensions.push_back({.name = VK_KHR_SURFACE_EXTENSION_NAME, .required = true});
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
// There are two possible APIs for X11 surfaces (SDL supports both)
// just try to enable both and hope SDL will be happy.
// Both are optional, in case one is not supported, but in the end
// one of them will be needed.
outExtensions.push_back({.name = "VK_KHR_xcb_surface", .required = false});
outExtensions.push_back({.name = "VK_KHR_xlib_surface", .required = false});
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
g_instanceExtensions.push_back({.name = "VK_KHR_win32_surface", .required = true});
#endif
}
}
void buildDefaultInstanceLayerList(std::vector<LayerInfo>& outLayers) noexcept
{
#if !defined(KAZAN_RELEASE)
outLayers.push_back({.name = "VK_LAYER_KHRONOS_validation", .required = false});
#else
(void) outLayers;
#endif
}
std::vector<const char*> checkInstanceExtensions(std::vector<ExtensionInfo>& extensions)
{
std::vector<vk::ExtensionProperties> properties = vk::enumerateInstanceExtensionProperties(nullptr);
auto isExtensionSupported = [&properties](const char* extension)
{
for (const vk::ExtensionProperties& props : properties)
{
if (std::strncmp(props.extensionName, extension, VK_MAX_EXTENSION_NAME_SIZE) == 0)
{
return true;
}
}
return false;
};
std::vector<const char*> enabledExtensions;
for (ExtensionInfo& extInfo : extensions)
{
const bool supported = isExtensionSupported(extInfo.name);
if (!supported && extInfo.required)
{
logAndDie("Required Vulkan instance extension not supported: {}.", extInfo.name);
}
if (supported) {
enabledExtensions.push_back(extInfo.name);
}
extInfo.enabled = supported;
}
return enabledExtensions;
}
std::vector<const char*> checkInstanceLayers(std::vector<LayerInfo>& layers)
{
std::vector<const char*> enabledLayers;
#if !defined(KAZAN_RELEASE)
std::vector<vk::LayerProperties> properties = vk::enumerateInstanceLayerProperties();
auto isLayerSupported = [&properties](const char* extension)
{
for (const vk::LayerProperties& props : properties)
{
if (std::strncmp(props.layerName, extension, VK_MAX_EXTENSION_NAME_SIZE) == 0)
{
return true;
}
}
return false;
};
for (LayerInfo& layerInfo : layers)
{
const bool supported = isLayerSupported(layerInfo.name);
if (!supported && layerInfo.required)
{
logAndDie("Required Vulkan instance layer not supported: {}.", layerInfo.name);
}
if (supported) {
enabledLayers.push_back(layerInfo.name);
}
layerInfo.enabled = supported;
}
#endif
return enabledLayers;
}
vk::Bool32 VKAPI_PTR vulkanMessengerCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT severity,
VkDebugUtilsMessageTypeFlagsEXT /* types */,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
void* /* pUserData */
)
{
switch (pCallbackData->messageIdNumber)
{
case 1303270965: // We use general layout to copy texture to mips. I know it's not optimal, but it's allowed and
// works for me ¯\_(ツ)_/¯
case 148949623: // The spec says that a) you can't create descriptor sets from layouts with the push-descriptor bit
// set and b) you have to bind a descriptor set when using said set layout in vkCmdPushDescriptorSetKHR.
// So you have to use a set, but can't create it... Something is off.
// Apart from that c) it also says that dstSet is ignored when using vkCmdPushDescriptorSetKHR, so
// maybe the validation layers are just wrong.
// a) https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDescriptorSetAllocateInfo.html#VUID-VkDescriptorSetAllocateInfo-pSetLayouts-00308
// b) https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkWriteDescriptorSet.html#VUID-VkWriteDescriptorSet-dstSet-00320
// c) https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdPushDescriptorSetKHR.html
return VK_FALSE;
}
logMsg("VK> {}", pCallbackData->pMessage);
if (/* g_breakOnVulkanError && */ severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT)
{
if (const mijin::Result<mijin::Stacktrace> trace = mijin::captureStacktrace(0); trace.isSuccess())
{
logMsg("{}", *trace);
}
MIJIN_TRAP();
}
return VK_FALSE;
}
std::pair<std::uint32_t, std::uint32_t> detectQueueFamilies(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR dummySurface)
{
std::vector<vk::QueueFamilyProperties> queueFamilyProperties = physicalDevice.getQueueFamilyProperties();
for (std::uint32_t idx = 0; idx < queueFamilyProperties.size(); ++idx)
{
const vk::QueueFamilyProperties& properties = queueFamilyProperties[idx];
const vk::Bool32 surfaceSupport = dummySurface ? physicalDevice.getSurfaceSupportKHR(idx, dummySurface) : VK_TRUE; // no dummy surface -> no surface support needed
if (surfaceSupport
&& (properties.queueFlags & vk::QueueFlagBits::eGraphics)
&& (properties.queueFlags & vk::QueueFlagBits::eCompute))
{
return std::make_pair(idx, idx);
}
}
return std::make_pair(std::numeric_limits<std::uint32_t>::max(), std::numeric_limits<std::uint32_t>::max());
}
PhysicalDeviceInfo getPhysicalDeviceInfo(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR dummySurface)
{
PhysicalDeviceInfo deviceInfo;
deviceInfo.device = physicalDevice;
deviceInfo.memoryProperties = physicalDevice.getMemoryProperties();
deviceInfo.extensions = physicalDevice.enumerateDeviceExtensionProperties();
auto isExtensionSupported = [&](const char* extension)
{
for (const vk::ExtensionProperties& props : deviceInfo.extensions)
{
if (std::strncmp(props.extensionName, extension, VK_MAX_EXTENSION_NAME_SIZE) == 0) {
return true;
}
}
return false;
};
const bool rayTracingSupported = isExtensionSupported(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME)
&& isExtensionSupported(VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME)
&& isExtensionSupported(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME);
const bool meshShadersSupported = isExtensionSupported(VK_EXT_MESH_SHADER_EXTENSION_NAME);
vk::PhysicalDeviceProperties2 deviceProperties = {
.pNext = &deviceInfo.rayTracingProperties
};
physicalDevice.getProperties2(&deviceProperties);
deviceInfo.properties = deviceProperties.properties;
#define ADD_TO_NEXT_CHAIN(struct_name) \
*ppNext = &(struct_name); ppNext = &(struct_name).pNext
vk::PhysicalDeviceFeatures2 deviceFeatures;
void** ppNext = &deviceFeatures.pNext;
ADD_TO_NEXT_CHAIN(deviceInfo.vulkan11Features);
ADD_TO_NEXT_CHAIN(deviceInfo.vulkan12Features);
ADD_TO_NEXT_CHAIN(deviceInfo.vulkan13Features);
if (rayTracingSupported)
{
ADD_TO_NEXT_CHAIN(deviceInfo.accelerationStructureFeatures);
ADD_TO_NEXT_CHAIN(deviceInfo.rayTracingPipelineFeatures);
}
if (meshShadersSupported) {
ADD_TO_NEXT_CHAIN(deviceInfo.meshShaderFeatures);
}
#undef ADD_TO_NEXT_CHAIN
physicalDevice.getFeatures2(&deviceFeatures);
deviceInfo.features = deviceFeatures.features;
// fill the renderer features
deviceInfo.availableFeatures.rayTracing = rayTracingSupported;
deviceInfo.availableFeatures.meshShaders = meshShadersSupported && deviceInfo.meshShaderFeatures.meshShader;
deviceInfo.surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(dummySurface);
std::tie(deviceInfo.graphicsQueueFamily, deviceInfo.computeQueueFamily) = detectQueueFamilies(physicalDevice, dummySurface);
return deviceInfo;
}
std::vector<PhysicalDeviceInfo> getPhysicalDeviceInfos(Instance& instance)
{
const std::vector<vk::PhysicalDevice> devices = instance.getVkHandle().enumeratePhysicalDevices();
std::vector<PhysicalDeviceInfo> deviceInfos;
deviceInfos.reserve(devices.size());
const ObjectPtr<Window> dummyWindow = instance.createWindow({.flags={.hidden=true}});
const vk::SurfaceKHR dummySurface = dummyWindow->getVkSurface();
for (const vk::PhysicalDevice device : devices)
{
deviceInfos.push_back(getPhysicalDeviceInfo(device, dummySurface));
}
return deviceInfos;
}
} // namespace
Instance::Instance(InstanceCreationArgs args)
: super_t(nullptr),
mExtensions(std::move(args.extensions)),
mLayers(std::move(args.layers))
{
setMainThread();
const AddonInitArgs addonInitArgs = {
.instance = getPointer(),
.instanceCreationArgs = args
};
for (Addon* addon : getAddons())
{
addon->init(addonInitArgs);
}
VULKAN_HPP_DEFAULT_DISPATCHER.init();
if (!args.flags.noDefaultExtensions)
{
buildDefaultInstanceExtensionList(mExtensions);
}
if (!args.flags.noDefaultLayers)
{
buildDefaultInstanceLayerList(mLayers);
}
const std::vector<const char*> enabledLayerNames = checkInstanceLayers(mLayers);
const std::vector<const char*> enabledExtensionNames = checkInstanceExtensions(mExtensions);
const vk::InstanceCreateInfo createInfo =
{
.pApplicationInfo = &args.applicationInfo,
.enabledLayerCount = static_cast<std::uint32_t>(enabledLayerNames.size()),
.ppEnabledLayerNames = enabledLayerNames.data(),
.enabledExtensionCount = static_cast<std::uint32_t>(enabledExtensionNames.size()),
.ppEnabledExtensionNames = enabledExtensionNames.data()
};
mHandle = vk::createInstance(createInfo);
VULKAN_HPP_DEFAULT_DISPATCHER.init(mHandle);
// dump info about enabled extensions and layers
logMsg("Enabled Vulkan instance extensions: {}", enabledExtensionNames);
logMsg("Enabled Vulkan instance layers: {}", enabledLayerNames);
// also dump the actual instance version
const std::uint32_t apiVersion = vk::enumerateInstanceVersion();
logMsg("Vulkan instance version: {}.{}.{}.\n",
VK_API_VERSION_MAJOR(apiVersion),
VK_API_VERSION_MINOR(apiVersion),
VK_API_VERSION_PATCH(apiVersion)
);
if (isExtensionEnabled(VK_EXT_DEBUG_UTILS_EXTENSION_NAME))
{
// create debug messenger
const vk::DebugUtilsMessengerCreateInfoEXT dumCreateInfo =
{
.messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError,
.messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation,
.pfnUserCallback = &vulkanMessengerCallback
};
mDebugMessenger = mHandle.createDebugUtilsMessengerEXT(dumCreateInfo);
}
mPhysicalDevices = getPhysicalDeviceInfos(*this);
mWorkerTaskLoop.start(10);
}
Instance::~Instance() noexcept
{
for (Addon* addon : getAddons())
{
addon->cleanup();
}
while (!mDeleteQueue.empty())
{
runDeleters(true);
}
if (mDebugMessenger)
{
mHandle.destroyDebugUtilsMessengerEXT(mDebugMessenger);
}
mHandle.destroy();
}
bool Instance::isExtensionEnabled(const char* name) const noexcept
{
for (const ExtensionInfo& extInfo : mExtensions)
{
if (std::strcmp(extInfo.name, name) == 0)
{
return extInfo.enabled;
}
}
return false;
}
bool Instance::isLayerEnabled(const char* name) const noexcept
{
for (const LayerInfo& layerInfo : mLayers)
{
if (std::strcmp(layerInfo.name, name) == 0)
{
return layerInfo.enabled;
}
}
return false;
}
ObjectPtr<Window> Instance::createWindow(const WindowCreationArgs& args)
{
return createChild<Window>(args);
}
ObjectPtr<Device> Instance::createDevice(DeviceCreationArgs args)
{
return createChild<Device>(std::move(args));
}
void Instance::queueDelete(deleter_t deleter) noexcept
{
MIJIN_ASSERT(deleter, "Don't pass an empty function into queueDelete()!");
if (!deleter)
{
return;
}
runOnMainThread([deleter = std::move(deleter), this]() mutable
{
static const int DELETE_DELAY_FRAMES = 5; // TODO: ?
mDeleteQueue.push_back({
.deleter = std::move(deleter),
.remainingFrames = DELETE_DELAY_FRAMES
});
});
}
void Instance::tickDeleteQueue()
{
for (DeleteQueueEntry& entry : mDeleteQueue)
{
--entry.remainingFrames;
}
runDeleters();
}
void Instance::setMainThread()
{
mMainThread = std::this_thread::get_id();
}
void Instance::requestQuit() noexcept
{
if (mQuitRequested)
{
return;
}
mQuitRequested = true;
quitRequested.emit();
mMainTaskLoop.addTask([](Instance* instance) -> mijin::Task<>
{
for (int tick = 0; tick < 100; ++tick)
{
if (instance->getMainTaskLoop().getNumTasks() == 1)
{
// just us left, bye bye!
co_return;
}
co_await mijin::c_suspend();
}
const std::size_t activeTasks = instance->getMainTaskLoop().getActiveTasks() - 1; // substract yourself
if (activeTasks > 0)
{
logMsg("{} tasks did not finish when shutting down!", activeTasks - 1);
#if MIJIN_COROUTINE_ENABLE_DEBUG_INFO
const std::vector<mijin::TaskHandle> tasks = instance->getMainTaskLoop().getAllTasks();
for (auto [index, handle] : mijin::enumerate(tasks))
{
if (handle == mijin::getCurrentTask())
{
continue;
}
const mijin::Optional<mijin::Stacktrace> stack = handle.getCreationStack();
if (!stack.empty())
{
logMsg("Task {} creation stack:\n{}", index, *stack);
}
}
#endif // MIJIN_COROUTINE_ENABLE_DEBUG_INFO
MIJIN_TRAP();
}
else
{
const std::size_t totalTasks = instance->getMainTaskLoop().getNumTasks() - 1;
logMsg("{} tasks still waiting on program exit.", totalTasks);
}
instance->getMainTaskLoop().cancelAllTasks();
co_return;
}(this));
}
void Instance::runDeleters(bool runAll)
{
for (auto it = mDeleteQueue.begin(); it != mDeleteQueue.end();)
{
if (runAll || it->remainingFrames <= 0)
{
it->deleter();
it = mDeleteQueue.erase(it);
}
else
{
++it;
}
}
}
}

207
source/io/bitmap.cpp Normal file
View File

@@ -0,0 +1,207 @@
#include "iwa/io/bitmap.hpp"
#include <cstring>
#include <filesystem>
#include <mijin/detect.hpp>
#if MIJIN_COMPILER == MIJIN_COMPILER_GCC || MIJIN_COMPILER == MIJIN_COMPILER_CLANG
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#endif // MIJIN_COMPILER == MIJIN_COMPILER_GCC || MIJIN_COMPILER == MIJIN_COMPILER_CLANG
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define STBI_FAILURE_USERMSG
#include <stb_image.h>
#include <stb_image_write.h>
#undef STB_IMAGE_IMPLEMENTATION
#undef STB_IMAGE_WRITE_IMPLEMENTATION
#if MIJIN_COMPILER == MIJIN_COMPILER_GCC || MIJIN_COMPILER == MIJIN_COMPILER_CLANG
#pragma GCC diagnostic pop
#endif // MIJIN_COMPILER == MIJIN_COMPILER_GCC || MIJIN_COMPILER == MIJIN_COMPILER_CLANG
#include "iwa/log.hpp"
namespace iwa
{
namespace
{
vk::Format formatByComponents(int components, ImageFormatType formatType)
{
switch (formatType)
{
case ImageFormatType::UNORM:
switch (components)
{
case 1:
return vk::Format::eR8Unorm;
case 2:
return vk::Format::eR8G8Unorm;
case 3:
return vk::Format::eR8G8B8Unorm;
case 4:
return vk::Format::eR8G8B8A8Unorm;
}
break;
case ImageFormatType::UINT:
switch (components)
{
case 1:
return vk::Format::eR8Uint;
case 2:
return vk::Format::eR8G8Uint;
case 3:
return vk::Format::eR8G8B8Uint;
case 4:
return vk::Format::eR8G8B8A8Uint;
}
break;
case ImageFormatType::SRGB:
switch (components)
{
case 1:
return vk::Format::eR8Srgb;
case 2:
return vk::Format::eR8G8Srgb;
case 3:
return vk::Format::eR8G8B8Srgb;
case 4:
return vk::Format::eR8G8B8A8Srgb;
}
break;
}
logAndDie("Could not detect image format.");
}
ObjectPtr<Bitmap> loadBitmapFromSTBI(stbi_uc* data, std::size_t width, std::size_t height, std::size_t /* components */,
const BitmapLoadOptions& options)
{
if (data == nullptr)
{
logAndDie("Could not load texture: {}", stbi_failure_reason());
}
const BitmapCreationArgs args =
{
.format = formatByComponents(/*components */ 4, options.formatType),
.size = {static_cast<unsigned>(width), static_cast<unsigned>(height)}
};
ObjectPtr <Bitmap> bitmap = Bitmap::create(args);
std::memcpy(bitmap->getData().data(), data, bitmap->getData().size());
stbi_image_free(data);
return bitmap;
}
int stbiReadCallback(void* user, char* data, int size)
{
mijin::Stream& stream = *static_cast<mijin::Stream*>(user);
std::size_t bytesRead = 0;
const mijin::StreamError error = stream.readRaw(data, size, /* partial = */ true, &bytesRead);
if (error != mijin::StreamError::SUCCESS)
{
// TODO: return what?
return 0;
}
return static_cast<int>(bytesRead);
}
void stbiSkipCallback(void* user, int bytes)
{
mijin::Stream& stream = *static_cast<mijin::Stream*>(user);
(void) stream.seek(bytes, mijin::SeekMode::RELATIVE);
}
int stbiEofCallback(void* user)
{
mijin::Stream& stream = *static_cast<mijin::Stream*>(user);
return stream.isAtEnd();
}
} // namespace
ObjectPtr<Bitmap> loadBitmap(const BitmapLoadOptions& options)
{
int width, height, components; // NOLINT(cppcoreguidelines-init-variables)
const stbi_io_callbacks callbacks{
.read = &stbiReadCallback,
.skip = &stbiSkipCallback,
.eof = &stbiEofCallback
};
stbi_uc* data = stbi_load_from_callbacks(
/* clbk = */ &callbacks,
/* user = */ options.stream,
/* x = */ &width,
/* y = */ &height,
/* channels_in_file = */ &components,
/* desired_channels = */ 4);
// NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage) Yes, width isn't initialized if STB fails. No, that is not a problem
return loadBitmapFromSTBI(data, width, height, components, options);
}
void saveBitmap(const Bitmap& bitmap, const BitmapSaveOptions& options)
{
namespace fs = std::filesystem;
BitmapCodec codec = options.codec;
if (codec == BitmapCodec::NONE)
{
std::string extension = fs::path(options.fileName).extension().string();
std::ranges::transform(extension, extension.begin(), [](char chr)
{ return static_cast<char>(std::tolower(chr)); });
if (extension == ".png")
{
codec = BitmapCodec::PNG;
} else if (extension == ".jpg" || extension == ".jpeg")
{
codec = BitmapCodec::JPEG;
} else
{
logAndDie("Couldn't guess image file codec from file extension: {}.", extension);
}
}
int comp = 0;
switch (bitmap.getFormat())
{
case vk::Format::eR8G8B8A8Unorm:
case vk::Format::eR8G8B8A8Srgb:
comp = 4;
break;
case vk::Format::eR8G8B8Unorm:
case vk::Format::eR8G8B8Srgb:
comp = 3;
break;
default:
logAndDie("Cannot write this image format (yet).");
break;
}
switch (codec)
{
case BitmapCodec::NONE:
assert(0);
break;
case BitmapCodec::PNG:
stbi_write_png(
/* filename = */ options.fileName.c_str(),
/* w = */ static_cast<int>(bitmap.getSize().width),
/* h = */ static_cast<int>(bitmap.getSize().height),
/* comp = */ comp,
/* data = */ bitmap.getData().data(),
/* stride_in_bytes = */ comp * static_cast<int>(bitmap.getSize().width));
break;
case BitmapCodec::JPEG:
stbi_write_jpg(
/* filename = */ options.fileName.c_str(),
/* x = */ static_cast<int>(bitmap.getSize().width),
/* y = */ static_cast<int>(bitmap.getSize().height),
/* comp = */ comp,
/* data = */ bitmap.getData().data(),
/* quality = */ options.jpegQuality
);
break;
}
}
} // namespace iwa

165
source/io/font.cpp Normal file
View File

@@ -0,0 +1,165 @@
#include "iwa/io/font.hpp"
#include <mijin/util/iterators.hpp>
#define STB_RECT_PACK_IMPLEMENTATION
#define STB_TRUETYPE_IMPLEMENTATION
#include <stb_rect_pack.h>
#include <stb_truetype.h>
#undef STB_RECT_PACK_IMPLEMENTATION
#undef STB_TRUETYPE_IMPLEMENTATION
namespace iwa
{
namespace
{
std::vector<int> getRenderCodePoints()
{
std::vector<int> result;
for (int chr = 32; chr <= 255; ++chr) {
result.push_back(chr);
}
return result;
}
}
ObjectPtr<Font> loadFont(const FontLoadOptions& options)
{
mijin::TypelessBuffer fontData;
if (const mijin::StreamError error = options.stream->readRest(fontData); error != mijin::StreamError::SUCCESS) {
throw std::runtime_error("IO error reading font from stream.");
}
// init font data
stbtt_fontinfo fontInfo;
const int fontOffset = stbtt_GetFontOffsetForIndex(static_cast<const unsigned char*>(fontData.data()), 0);
stbtt_InitFont(&fontInfo, static_cast<const unsigned char*>(fontData.data()), fontOffset);
// prepare data for packing
std::vector<int> codepoints = getRenderCodePoints();
std::vector<stbtt_packedchar> chardata;
std::vector<stbrp_rect> rects;
chardata.resize(codepoints.size());
rects.resize(codepoints.size());
stbtt_pack_range range = {
.font_size = options.size,
.first_unicode_codepoint_in_range = 0,
.array_of_unicode_codepoints = codepoints.data(),
.num_chars = static_cast<int>(codepoints.size()),
.chardata_for_range = chardata.data()
};
// TODO: this is just a guess, maybe there is a better way to detect this
int oversample = 1;
if (options.size < 30) {
oversample = 4;
}
else if (options.size < 35) {
oversample = 3;
}
else if (options.size < 40) {
oversample = 4;
}
int textureSize = 64;
mijin::TypelessBuffer pixels;
while (textureSize <= 4096)
{
pixels.resize(textureSize * textureSize * sizeof(unsigned char)); // NOLINT
// reset char data
for (stbtt_packedchar& packedChar : chardata) {
packedChar.x0 = packedChar.y0 = packedChar.x1 = packedChar.y1 = 0;
}
// try packing with this size
stbtt_pack_context packContext;
stbtt_PackBegin(
/* spc = */ &packContext,
/* pixels = */ static_cast<unsigned char*>(pixels.data()),
/* width = */ textureSize,
/* height = */ textureSize,
/* stride_in_bytes = */ 0,
/* padding = */ 1,
/* alloc_context = */ nullptr
);
stbtt_PackSetOversampling(&packContext, oversample, oversample); // TODO: make adjustable for better font quality
const int nRects = stbtt_PackFontRangesGatherRects(&packContext, &fontInfo, &range, 1, rects.data());
stbtt_PackFontRangesPackRects(&packContext, rects.data(), nRects);
bool allPacked = true;
for (int rectIdx = 0; rectIdx < nRects; ++rectIdx)
{
if (!rects[rectIdx].was_packed) {
allPacked = false;
break;
}
}
if (!allPacked)
{
textureSize *= 2;
continue;
}
stbtt_PackFontRangesRenderIntoRects(
/* spc = */ &packContext,
/* info = */ &fontInfo,
/* ranges = */ &range,
/* num_ranges = */ 1,
/* rects = */ rects.data()
);
stbtt_PackEnd(&packContext);
break;
}
// now create a bitmap from pixels
ObjectPtr<Bitmap> bitmap = Bitmap::create(BitmapCreationArgs{
.format = vk::Format::eR8Unorm,
.size = {
.width = static_cast<std::uint32_t>(textureSize),
.height = static_cast<std::uint32_t>(textureSize)
},
.initialData = std::move(pixels)
});
std::unordered_map<char32_t, GlyphInfo> glyphMap;
glyphMap.reserve(chardata.size());
for (auto [codepoint, chrdata] : mijin::zip(codepoints, chardata))
{
glyphMap.emplace(static_cast<char32_t>(codepoint), GlyphInfo{
.uvPos0 = {
static_cast<float>(chrdata.x0) / static_cast<float>(textureSize),
static_cast<float>(chrdata.y0) / static_cast<float>(textureSize)
},
.uvPos1 = {
static_cast<float>(chrdata.x1) / static_cast<float>(textureSize),
static_cast<float>(chrdata.y1) / static_cast<float>(textureSize)
},
.xOffsetBefore = chrdata.xoff,
.xOffsetAfter = chrdata.xoff2,
.yOffsetBefore = chrdata.yoff,
.yOffsetAfter = chrdata.yoff2,
.xAdvance = chrdata.xadvance
});
}
const float scale = options.size > 0 ? stbtt_ScaleForPixelHeight(&fontInfo, options.size) : stbtt_ScaleForMappingEmToPixels(&fontInfo, -options.size);
int ascent = 0, descent = 0, lineGap = 0;
stbtt_GetFontVMetrics(&fontInfo, &ascent, &descent, &lineGap);
return Font::create(FontCreationArgs{
.bitmap = std::move(bitmap),
.glyphMap = std::move(glyphMap),
.metrics = {
.ascent = scale * static_cast<float>(ascent),
.descent = scale * static_cast<float>(descent),
.lineGap = scale * static_cast<float>(lineGap),
.sizeFactor = 1.f / static_cast<float>(oversample)
}
});
}
} // namespace iwa

6
source/io/mesh.cpp Normal file
View File

@@ -0,0 +1,6 @@
#include "iwa/io/mesh.hpp"
namespace iwa
{
} // namespace iwa

130
source/object.cpp Normal file
View File

@@ -0,0 +1,130 @@
#include "iwa/object.hpp"
#if IWA_OBJECTPTR_TRACKING
#include <iostream>
#endif
#include <mutex>
#include <shared_mutex>
#include <unordered_map>
namespace iwa::impl
{
namespace
{
std::atomic<object_id_t> gNextObjectId;
std::unordered_map<object_id_t, BaseObject*> gAllObjects;
std::shared_mutex gAllObjectMutex;
std::unordered_map<object_id_t, object_destruction_handler_t> gDestructionHandlers;
std::shared_mutex gDestructionHandlersMutex;
#if IWA_OBJECTPTR_TRACKING
std::list<ObjectPtrAllocation> gObjectPtrAllocations;
std::mutex gObjectPtrAllocationsMutex;
struct ObjectPtrAllocationsChecker
{
~ObjectPtrAllocationsChecker()
{
const std::unique_lock lock(gObjectPtrAllocationsMutex);
if (!gObjectPtrAllocations.empty())
{
std::cerr << "ObjectPtrs pending deletion:\n";
for (const ObjectPtrAllocation& allocation : gObjectPtrAllocations)
{
std::cerr << typeid(*allocation.object).name() /* << "@" << allocation.stacktrace */ <<"\n";
#if IWA_OBJECTPTR_TRACKING > 1
if (allocation.stacktrace.isSuccess())
{
std::cerr << *allocation.stacktrace << "\n";
}
#endif
}
std::cerr.flush();
MIJIN_TRAP();
}
}
} gObjectPtrAllocationsChecker;
#endif // IWA_OBJECTPTR_TRACKING
}
#if IWA_OBJECTPTR_TRACKING
#if IWA_OBJECTPTR_TRACKING > 1
objectptr_allocation_handle_t trackObjectPtr(BaseObject* object, mijin::Result<mijin::Stacktrace>&& stacktrace) noexcept
{
const std::unique_lock lock(gObjectPtrAllocationsMutex);
gObjectPtrAllocations.emplace_back(object, std::move(stacktrace));
return std::prev(gObjectPtrAllocations.end());
}
#else
objectptr_allocation_handle_t trackObjectPtr(BaseObject* object) noexcept
{
const std::unique_lock lock(gObjectPtrAllocationsMutex);
gObjectPtrAllocations.emplace_back(object);
return std::prev(gObjectPtrAllocations.end());
}
#endif
void untrackObjectPtr(objectptr_allocation_handle_t handle) noexcept
{
const std::unique_lock lock(gObjectPtrAllocationsMutex);
gObjectPtrAllocations.erase(*handle); // NOLINT(bugprone-unchecked-optional-access)
}
#endif // IWA_OBJECTPTR_TRACKING
object_id_t nextObjectId() noexcept
{
return ++gNextObjectId;
}
void registerObject(BaseObject* object) noexcept
{
const std::unique_lock lock(gAllObjectMutex);
MIJIN_ASSERT(gAllObjects.find(object->getId()) == gAllObjects.end(), "Duplicate object id!");
gAllObjects.emplace(object->getId(), object);
}
void unregisterObject(BaseObject* object) noexcept
{
{
const std::unique_lock lock(gAllObjectMutex);
gAllObjects.erase(object->getId());
}
std::unordered_map<object_id_t, object_destruction_handler_t>::iterator itHandler;
{
const std::shared_lock readLock(gDestructionHandlersMutex);
itHandler = gDestructionHandlers.find(object->getId());
}
if (itHandler != gDestructionHandlers.end())
{
object_destruction_handler_t handler;
{
const std::unique_lock writeLock(gDestructionHandlersMutex);
handler = std::move(itHandler->second);
gDestructionHandlers.erase(itHandler);
}
handler();
}
}
ObjectPtr<BaseObject> getRegisteredObject(object_id_t objectId) noexcept
{
const std::shared_lock lock(gAllObjectMutex);
auto it = gAllObjects.find(objectId);
if (it != gAllObjects.end()) {
return it->second->getPointer();
}
return {};
}
} // namespace iwa::impl
namespace iwa
{
void registerObjectDestructionHandler(const BaseObject& object, object_destruction_handler_t handler) noexcept
{
const std::unique_lock writeLock(impl::gDestructionHandlersMutex);
impl::gDestructionHandlers.emplace(object.getId(), std::move(handler));
} // namespace iwa
}

136
source/pipeline.cpp Normal file
View File

@@ -0,0 +1,136 @@
#include "iwa/pipeline.hpp"
#include <mijin/io/stream.hpp>
#include <yaml-cpp/yaml.h>
#include "iwa/device.hpp"
#include "iwa/util/glsl_compiler.hpp"
#include "iwa/util/next_chain.hpp"
namespace iwa
{
PipelineLayout::PipelineLayout(ObjectPtr<Device> owner, const PipelineLayoutCreationArgs& args) : super_t(std::move(owner))
{
std::vector<vk::DescriptorSetLayout> setLayoutHandles;
setLayoutHandles.reserve(args.setLayouts.size());
for (const ObjectPtr<DescriptorSetLayout>& setLayout : args.setLayouts)
{
setLayoutHandles.push_back(setLayout->getVkHandle());
}
mHandle = getOwner()->getVkHandle().createPipelineLayout(vk::PipelineLayoutCreateInfo{
.flags = args.flags,
.setLayoutCount = static_cast<std::uint32_t>(setLayoutHandles.size()),
.pSetLayouts = setLayoutHandles.data(),
.pushConstantRangeCount = static_cast<std::uint32_t>(args.pushConstantRanges.size()),
.pPushConstantRanges = args.pushConstantRanges.data()
});
}
PipelineLayout::~PipelineLayout() noexcept
{
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyPipelineLayout)
}
Pipeline::Pipeline(ObjectPtr<Device> owner) noexcept : super_t(std::move(owner))
{
}
Pipeline::~Pipeline() noexcept
{
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyPipeline)
}
GraphicsPipeline::GraphicsPipeline(ObjectPtr<Device> owner, const GraphicsPipelineCreationArgs& args) noexcept
: super_t(std::move(owner))
{
std::vector<vk::PipelineShaderStageCreateInfo> vkStages;
for (const PipelineStage& stage : args.stages)
{
vk::PipelineShaderStageCreateInfo& vkStage = vkStages.emplace_back();
vkStage.stage = stage.stage;
vkStage.module = *stage.shader;
vkStage.pName = stage.name.c_str();
}
const vk::PipelineVertexInputStateCreateInfo vkVertexInput =
{
.vertexBindingDescriptionCount = static_cast<std::uint32_t>(args.vertexInput.bindings.size()),
.pVertexBindingDescriptions = args.vertexInput.bindings.data(),
.vertexAttributeDescriptionCount = static_cast<std::uint32_t>(args.vertexInput.attributes.size()),
.pVertexAttributeDescriptions = args.vertexInput.attributes.data()
};
const vk::PipelineColorBlendStateCreateInfo vkColorBlend =
{
.logicOpEnable = args.colorBlend.logicOp.has_value(),
.logicOp = args.colorBlend.logicOp.has_value() ? *args.colorBlend.logicOp : vk::LogicOp(),
.attachmentCount = static_cast<std::uint32_t>(args.colorBlend.attachements.size()),
.pAttachments = args.colorBlend.attachements.data()
};
const vk::PipelineDynamicStateCreateInfo vkDynamic =
{
.dynamicStateCount = static_cast<std::uint32_t>(args.dynamicState.size()),
.pDynamicStates = args.dynamicState.data()
};
NextChain nextChain;
if (args.renderingInfo.has_value())
{
const GraphicsPipelineRenderingInfo& rinfo = *args.renderingInfo;
nextChain.append(vk::PipelineRenderingCreateInfo{
.viewMask = rinfo.viewMask,
.colorAttachmentCount = static_cast<std::uint32_t>(rinfo.colorAttachmentFormats.size()),
.pColorAttachmentFormats = rinfo.colorAttachmentFormats.data(),
.depthAttachmentFormat = rinfo.depthFormat,
.stencilAttachmentFormat = rinfo.stencilFormat
});
}
const vk::GraphicsPipelineCreateInfo vkCreateInfo =
{
.pNext = nextChain.finalize(),
.stageCount = static_cast<std::uint32_t>(vkStages.size()),
.pStages = vkStages.data(),
.pVertexInputState = &vkVertexInput,
.pInputAssemblyState = &args.inputAssembly,
.pViewportState = &args.viewport,
.pRasterizationState = &args.rasterization,
.pMultisampleState = &args.multisample,
.pDepthStencilState = &args.depthStencil,
.pColorBlendState = &vkColorBlend,
.pDynamicState = &vkDynamic,
.layout = *args.layout,
.renderPass = args.renderPass ? *args.renderPass : vk::RenderPass(),
.subpass = args.subpass
};
const auto result = getOwner()->getVkHandle().createGraphicsPipeline(VK_NULL_HANDLE, vkCreateInfo);
MIJIN_ASSERT(result.result == vk::Result::eSuccess, "Graphics pipeline creation failed.");
mHandle = result.value;
}
ComputePipeline::ComputePipeline(ObjectPtr<Device> owner, const ComputePipelineCreationArgs& args) noexcept
: super_t(std::move(owner))
{
const vk::ComputePipelineCreateInfo vkCreateInfo{
.stage = {
.stage = vk::ShaderStageFlagBits::eCompute,
.module = *args.stage.shader,
.pName = args.stage.name.c_str()
},
.layout = *args.layout
};
const vk::ResultValue<vk::Pipeline> result = getOwner()->getVkHandle().createComputePipeline(VK_NULL_HANDLE, vkCreateInfo);
MIJIN_ASSERT(result.result == vk::Result::eSuccess, "Compute pipeline creation failed.");
mHandle = result.value;
}
RayTracingPipeline::RayTracingPipeline(ObjectPtr<Device> owner, const RayTracingPipelineCreationArgs& args) noexcept
: super_t(std::move(owner))
{
(void) args;
}
} // namespace iwa

70
source/render_pass.cpp Normal file
View File

@@ -0,0 +1,70 @@
#include "iwa/render_pass.hpp"
#include "iwa/device.hpp"
#include "iwa/image.hpp"
namespace iwa
{
RenderPass::RenderPass(ObjectPtr<Device> owner, const RenderPassCreationArgs& args) : super_t(std::move(owner))
{
std::vector<vk::SubpassDescription> vkSubpasses;
vkSubpasses.reserve(args.subpasses.size());
for (const SubpassDescription& subpass : args.subpasses)
{
MIJIN_ASSERT(subpass.resolveAttachments.empty() || subpass.resolveAttachments.size() == subpass.colorAttachments.size(),
"Number of resolve attachments must be either 0 or the same as color attachments.");
vkSubpasses.push_back({
.flags = subpass.flags,
.pipelineBindPoint = subpass.pipelineBindPoint,
.inputAttachmentCount = static_cast<std::uint32_t>(subpass.inputAttachments.size()),
.pInputAttachments = subpass.inputAttachments.data(),
.colorAttachmentCount = static_cast<std::uint32_t>(subpass.colorAttachments.size()),
.pColorAttachments = subpass.colorAttachments.data(),
.pResolveAttachments = subpass.resolveAttachments.data(),
.pDepthStencilAttachment = subpass.depthStencilAttachment.has_value() ? &subpass.depthStencilAttachment.value() : nullptr,
.preserveAttachmentCount = static_cast<std::uint32_t>(subpass.preserveAttachments.size()),
.pPreserveAttachments = subpass.preserveAttachments.data()
});
}
mHandle = getOwner()->getVkHandle().createRenderPass(vk::RenderPassCreateInfo{
.flags = args.flags,
.attachmentCount = static_cast<std::uint32_t>(args.attachments.size()),
.pAttachments = args.attachments.data(),
.subpassCount = static_cast<std::uint32_t>(vkSubpasses.size()),
.pSubpasses = vkSubpasses.data(),
.dependencyCount = static_cast<std::uint32_t>(args.dependencies.size()),
.pDependencies = args.dependencies.data()
});
}
RenderPass::~RenderPass() noexcept
{
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyRenderPass);
}
Framebuffer::Framebuffer(ObjectPtr<Device> owner, const FramebufferCreationArgs& args)
: super_t(std::move(owner)), mImageViews(args.attachments)
{
std::vector<vk::ImageView> vkImageViews;
vkImageViews.reserve(mImageViews.size());
for (const ObjectPtr<ImageView>& imageView : mImageViews)
{
vkImageViews.push_back(*imageView);
}
mHandle = getOwner()->getVkHandle().createFramebuffer(vk::FramebufferCreateInfo{
.flags = args.flags,
.renderPass = *args.renderPass,
.attachmentCount = static_cast<std::uint32_t>(vkImageViews.size()),
.pAttachments = vkImageViews.data(),
.width = args.width,
.height = args.height,
.layers = args.layers
});
}
Framebuffer::~Framebuffer() noexcept
{
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyFramebuffer);
}
} // namespace iwa

305
source/resource/bitmap.cpp Normal file
View File

@@ -0,0 +1,305 @@
#include "iwa/resource/bitmap.hpp"
#include <cmath>
#include "iwa/log.hpp"
#include "iwa/util/vkutil.hpp"
namespace iwa
{
enum class InterpolationMethod
{
NEAREST
};
class BitmapViewBase
{
protected:
Bitmap* mBitmap;
protected:
explicit inline BitmapViewBase(Bitmap* bitmap_) : mBitmap(bitmap_) {}
public:
BitmapViewBase(const BitmapViewBase&) = delete;
BitmapViewBase(BitmapViewBase&&) = default;
BitmapViewBase& operator=(const BitmapViewBase&) = delete;
BitmapViewBase& operator=(BitmapViewBase&&) = default;
virtual ~BitmapViewBase() = default; // just to make clang happy, should be optimised out again
[[nodiscard]] virtual glm::vec4 getPixel(unsigned x, unsigned y) const = 0;
[[nodiscard]] virtual glm::vec4 getPixelRelative(float x, float y, InterpolationMethod interpolation = InterpolationMethod::NEAREST) const = 0;
[[nodiscard]] virtual std::vector<glm::vec4> getPixels(unsigned x, unsigned y, unsigned width, unsigned height) const = 0;
virtual void fill(const glm::vec4& color) = 0;
virtual void copyChannels(const Bitmap& other, const std::vector<ChannelMapping>& mappings) = 0;
virtual void multiply(const glm::vec4& color) = 0;
};
template<typename TData, int numComponents, bool normalized = std::is_floating_point_v<TData>>
class BitmapView : public BitmapViewBase
{
private:
static constexpr std::size_t pixelSize = sizeof(TData) * numComponents;
public:
explicit inline BitmapView(Bitmap* bitmap_) : BitmapViewBase(bitmap_) {}
TData* getPixelRaw(unsigned x, unsigned y)
{
const std::size_t offset = x + y * mBitmap->getSize().width;
return reinterpret_cast<TData*>(&mBitmap->getData()[offset * pixelSize]);
}
[[nodiscard]] const TData* getPixelRaw(unsigned x, unsigned y) const
{
const std::size_t offset = x + y * mBitmap->getSize().width;
return reinterpret_cast<const TData*>(&mBitmap->getData()[offset * pixelSize]);
}
[[nodiscard]] TData convertChannel(float channel) const
{
if constexpr (normalized) {
return static_cast<TData>(channel);
} else {
return static_cast<TData>(255 * channel);
}
}
[[nodiscard]] float convertChannelBack(TData channel) const
{
if constexpr (normalized) {
return static_cast<float>(channel);
}
else {
return channel / 255.f;
}
}
[[nodiscard]] TData clampChannel(TData value)
{
if constexpr (normalized) {
return std::clamp(value, TData(0), TData(1));
}
else {
return std::clamp(value, TData(0), TData(255));
}
}
void convertColor(const glm::vec4& color, std::array<TData, numComponents>& outConverted) const
{
for (int comp = 0; comp < numComponents; ++comp)
{
outConverted[comp] = convertChannel(color[comp]);
}
}
[[nodiscard]] glm::vec4 convertColorBack(const TData* raw) const
{
glm::vec4 result;
for (int comp = 0; comp < numComponents; ++comp)
{
result[comp] = convertChannelBack(raw[comp]);
}
return result;
}
// overrides
[[nodiscard]] glm::vec4 getPixel(unsigned x, unsigned y) const override
{
const TData* pixelRaw = getPixelRaw(x, y);
return convertColorBack(pixelRaw);
}
[[nodiscard]] glm::vec4 getPixelRelative(float x, float y, InterpolationMethod interpolation = InterpolationMethod::NEAREST) const override
{
const vk::Extent2D size = mBitmap->getSize();
(void) interpolation; // TODO
const unsigned myX = std::clamp(static_cast<unsigned>(std::round(x * static_cast<float>(size.width))), 0u, size.width - 1);
const unsigned myY = std::clamp(static_cast<unsigned>(std::round(y * static_cast<float>(size.height))), 0u, size.height - 1);
return getPixel(myX, myY);
}
[[nodiscard]] std::vector<glm::vec4> getPixels(unsigned x, unsigned y, unsigned width, unsigned height) const override
{
std::vector<glm::vec4> pixels;
pixels.resize(static_cast<std::size_t>(width) * height);
for (unsigned ypos = y; ypos < y + height; ++ypos)
{
for (unsigned xpos = x; xpos < x + width; ++xpos)
{
pixels[xpos + height * ypos] = getPixel(xpos, ypos);
}
}
return pixels;
}
void fill(const glm::vec4& color) override
{
std::array<TData, numComponents> convertedColor; // NOLINT(cppcoreguidelines-pro-type-member-init)
convertColor(color, convertedColor);
for (unsigned y = 0; y < mBitmap->getSize().height; ++y)
{
for (unsigned x = 0; x < mBitmap->getSize().width; ++x)
{
TData* pixel = getPixelRaw(x, y);
for (int comp = 0; comp < numComponents; ++comp)
{
pixel[comp] = convertedColor[comp];
}
}
}
};
// TODO: maybe introduce a "dual-mView" class that includes both types at once?
void copyChannels(const Bitmap& other, const std::vector<ChannelMapping>& mappings) override
{
const vk::Extent2D size = mBitmap->getSize();
const std::vector<glm::vec4> otherPixels = other.getAllPixels();
if(otherPixels.size() == static_cast<std::size_t>(size.width) * size.height)
{
for (unsigned y = 0; y < size.height; ++y)
{
for (unsigned x = 0; x < size.width; ++x)
{
const glm::vec4 color = otherPixels[x + y * size.height];
for (const ChannelMapping& mapping : mappings)
{
assert(static_cast<int>(mapping.to) < numComponents);
TData* myPixel = getPixelRaw(x, y);
myPixel[static_cast<int>(mapping.to)] = convertChannel(color[static_cast<int>(mapping.from)]);
}
}
}
}
else
{
const vk::Extent2D otherSize = other.getSize();
const float factorX = static_cast<float>(otherSize.width) / static_cast<float>(size.width);
const float factorY = static_cast<float>(otherSize.height) / static_cast<float>(size.height);
for (unsigned y = 0; y < size.height; ++y)
{
for (unsigned x = 0; x < size.width; ++x)
{
const unsigned otherX = std::clamp(static_cast<unsigned>(std::round(factorX * static_cast<float>(x))), 0u, otherSize.width - 1);
const unsigned otherY = std::clamp(static_cast<unsigned>(std::round(factorY * static_cast<float>(x))), 0u, otherSize.height - 1);
const glm::vec4 color = otherPixels[otherX + otherY * size.height];
for (const ChannelMapping& mapping : mappings)
{
assert(static_cast<int>(mapping.to) < numComponents);
TData* myPixel = getPixelRaw(x, y);
myPixel[static_cast<int>(mapping.to)] = convertChannel(color[static_cast<int>(mapping.from)]);
}
}
}
}
}
void multiply(const glm::vec4& color) override
{
for (unsigned y = 0; y < mBitmap->getSize().height; ++y)
{
for (unsigned x = 0; x < mBitmap->getSize().width; ++x)
{
TData* pixel = getPixelRaw(x, y);
for (int comp = 0; comp < numComponents; ++comp)
{
pixel[comp] = clampChannel(static_cast<TData>(pixel[comp] * color[comp]));
}
}
}
}
};
void Bitmap::createView()
{
switch(mFormat)
{
case vk::Format::eR8Uint:
case vk::Format::eR8Unorm:
case vk::Format::eR8Srgb:
mView = std::make_unique<BitmapView<std::uint8_t, 1>>(this);
break;
case vk::Format::eR8G8Uint:
case vk::Format::eR8G8Unorm:
case vk::Format::eR8G8Srgb:
mView = std::make_unique<BitmapView<std::uint8_t, 2>>(this);
break;
case vk::Format::eR8G8B8Uint:
case vk::Format::eR8G8B8Unorm:
case vk::Format::eR8G8B8Srgb:
mView = std::make_unique<BitmapView<std::uint8_t, 3>>(this);
break;
case vk::Format::eR8G8B8A8Uint:
case vk::Format::eR8G8B8A8Unorm:
case vk::Format::eR8G8B8A8Srgb:
mView = std::make_unique<BitmapView<std::uint8_t, 4>>(this);
break;
case vk::Format::eR32Sfloat:
mView = std::make_unique<BitmapView<float, 1>>(this);
break;
case vk::Format::eR32G32Sfloat:
mView = std::make_unique<BitmapView<float, 2>>(this);
break;
case vk::Format::eR32G32B32Sfloat:
mView = std::make_unique<BitmapView<float, 3>>(this);
break;
case vk::Format::eR32G32B32A32Sfloat:
mView = std::make_unique<BitmapView<float, 4>>(this);
break;
default:
logAndDie("Missing format for bitmap mView!");
}
}
//
// public functions
//
Bitmap::Bitmap(BitmapCreationArgs args, ObjectPtr<BaseObject> owner)
: super_t(std::move(owner)), mFormat(args.format), mSize(args.size)
{
const std::size_t dataSize = static_cast<std::size_t>(args.size.width) * args.size.height * vkFormatSize(args.format);
if (args.initialData.empty())
{
mData.resize(dataSize);
}
else
{
MIJIN_ASSERT(args.initialData->byteSize() == dataSize, "Bitmap initial data size is invalid.");
mData = std::move(*args.initialData);
}
createView();
}
Bitmap::~Bitmap() // NOLINT(modernize-use-equals-default)
{
// needs to be implemented for the mView to be deleted correctly
}
glm::vec4 Bitmap::getPixel(unsigned int x, unsigned int y) const
{
return mView->getPixel(x, y);
}
std::vector<glm::vec4> Bitmap::getPixels(unsigned x, unsigned y, unsigned width, unsigned height) const
{
return mView->getPixels(x, y, width, height);
}
void Bitmap::fill(const glm::vec4& color)
{
mView->fill(color);
}
void Bitmap::copyChannels(const Bitmap& other, const std::vector<ChannelMapping>& mappings)
{
mView->copyChannels(other, mappings);
}
void Bitmap::multiply(const glm::vec4& color)
{
mView->multiply(color);
}
} // namespace iwa

11
source/resource/font.cpp Normal file
View File

@@ -0,0 +1,11 @@
#include "iwa/resource/font.hpp"
namespace iwa
{
Font::Font(FontCreationArgs args)
: mBitmap(std::move(args.bitmap)), mGlyphMap(std::move(args.glyphMap)), mMetrics(args.metrics)
{
}
} // namespace iwa

19
source/semaphore.cpp Normal file
View File

@@ -0,0 +1,19 @@
#include "iwa/semaphore.hpp"
#include "iwa/device.hpp"
namespace iwa
{
Semaphore::Semaphore(ObjectPtr<Device> owner, const SemaphoreCreationArgs& args) : super_t(std::move(owner))
{
mHandle = getOwner()->getVkHandle().createSemaphore(vk::SemaphoreCreateInfo{
.flags = args.flags
});
}
Semaphore::~Semaphore() noexcept
{
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroySemaphore)
}
} // namespace iwa

20
source/shader_module.cpp Normal file
View File

@@ -0,0 +1,20 @@
#include "iwa/shader_module.hpp"
#include "iwa/device.hpp"
namespace iwa
{
ShaderModule::ShaderModule(ObjectPtr<Device> owner, const ShaderModuleCreationArgs& args) : super_t(std::move(owner))
{
mHandle = getOwner()->getVkHandle().createShaderModule(vk::ShaderModuleCreateInfo{
.codeSize = static_cast<std::uint32_t>(args.code.size_bytes()),
.pCode = args.code.data()
});
}
ShaderModule::~ShaderModule() noexcept
{
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroyShaderModule)
}
} // namespace iwa

316
source/swapchain.cpp Normal file
View File

@@ -0,0 +1,316 @@
#include "iwa/swapchain.hpp"
#include <mijin/debug/assert.hpp>
#include <SDL_vulkan.h>
#include "iwa/device.hpp"
#include "iwa/log.hpp"
namespace iwa
{
namespace
{
std::pair<vk::PresentModeKHR, std::uint32_t> detectPresentMode(const Device& device, const Window& window, const vk::SurfaceCapabilitiesKHR& surfaceCapabilities)
{
std::vector<vk::PresentModeKHR> presentModes = device.getVkPhysicalDevice().getSurfacePresentModesKHR(window.getVkSurface());
vk::PresentModeKHR presentMode = vk::PresentModeKHR::eImmediate;
std::uint32_t imageCount = 2;
if (std::ranges::find(presentModes, vk::PresentModeKHR::eMailbox) != presentModes.end())
{
presentMode = vk::PresentModeKHR::eMailbox;
imageCount = 3;
}
imageCount = std::max(imageCount, surfaceCapabilities.minImageCount);
if (surfaceCapabilities.maxImageCount > 0)
{
imageCount = std::min(imageCount, surfaceCapabilities.maxImageCount);
}
return std::make_pair(presentMode, imageCount);
}
vk::SurfaceFormatKHR detectImageFormat(const Device& device, const Window& window, const vk::ImageUsageFlags imageUsage)
{
std::vector<vk::SurfaceFormatKHR> surfaceFormats = device.getVkPhysicalDevice().getSurfaceFormatsKHR(window.getVkSurface());
int resultIdx = -1;
for (std::size_t idx = 0; idx < surfaceFormats.size(); ++idx)
{
const vk::SurfaceFormatKHR surfaceFormat = surfaceFormats[idx];
vk::ImageFormatProperties formatProperties;
try
{
formatProperties = device.getVkPhysicalDevice().getImageFormatProperties(
/* format = */ surfaceFormat.format,
/* type = */ vk::ImageType::e2D,
/* tiling = */ vk::ImageTiling::eOptimal,
/* usage = */ imageUsage
);
}
catch(vk::FormatNotSupportedError&)
{
continue; // not supported
}
if (resultIdx < 0)
{
resultIdx = static_cast<int>(idx);
}
else if (surfaceFormat.format == vk::Format::eB8G8R8A8Srgb
&& surfaceFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear)
{
resultIdx = static_cast<int>(idx);
break;
}
}
if (resultIdx < 0)
{
logAndDie("Error creating Vulkan swapchain, no compatible image format found.");
}
return surfaceFormats[resultIdx];
}
vk::Extent2D detectImageExtent(const Window& window, const vk::SurfaceCapabilitiesKHR& surfaceCapabilities)
{
// default to current extent (chosen by the driver)
vk::Extent2D extent = surfaceCapabilities.currentExtent;
// alternatively ask SDL about it
if (extent.width == 0xFFFFFFFF && extent.height == 0xFFFFFFFF)
{
int width, height; // NOLINT(readability-isolate-declaration, cppcoreguidelines-init-variables)
SDL_Vulkan_GetDrawableSize(window.getSDLWindow(), &width, &height);
extent.width = std::clamp(static_cast<std::uint32_t>(width), surfaceCapabilities.minImageExtent.width, surfaceCapabilities.maxImageExtent.width);
extent.height = std::clamp(static_cast<std::uint32_t>(height), surfaceCapabilities.minImageExtent.height, surfaceCapabilities.maxImageExtent.height);
}
return extent;
}
} // namespace
Swapchain::Swapchain(ObjectPtr<Device> owner, SwapchainCreationArgs args)
: super_t(std::move(owner)), mWindow(std::move(args.window)), mImageUsage(args.imageUsage)
{
MIJIN_ASSERT(mWindow, "Invalid SwapchainCreationArgs: window cannot be NULL.");
mImageAvailableSemaphores.resize(args.parallelFrames);
recreate();
}
Swapchain::~Swapchain() noexcept
{
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroySwapchainKHR)
}
mijin::Task<> Swapchain::c_present(const PresentArgs& args)
{
while (!mWindow->isVisible()) {
co_await mijin::c_suspend();
}
const vk::PresentInfoKHR presentInfo =
{
.waitSemaphoreCount = static_cast<std::uint32_t>(args.waitSemaphores.size()),
.pWaitSemaphores = args.waitSemaphores.data(),
.swapchainCount = 1,
.pSwapchains = &mHandle,
.pImageIndices = &mCurrentImageIdx
};
vk::Result result; // NOLINT(cppcoreguidelines-init-variables)
try {
result = args.queue.presentKHR(presentInfo);
} catch(vk::OutOfDateKHRError&) {
result = vk::Result::eErrorOutOfDateKHR;
}
mCurrentImageIdx = INVALID_IMAGE_INDEX;
// next image, please
mCurrentFrameIdx = (mCurrentFrameIdx + 1) % static_cast<int>(getNumParallelFrames());
if (result == vk::Result::eSuccess) {
co_await c_acquireImage();
}
else {
recreate();
}
}
void Swapchain::recreate()
{
const vk::SurfaceCapabilitiesKHR surfaceCapabilities = getOwner()->getVkPhysicalDevice().getSurfaceCapabilitiesKHR(mWindow->getVkSurface());
const bool firstCreate = !mHandle;
// doing this here seems to fix the device loss during recreation
getOwner()->getVkHandle().waitIdle();
vk::SwapchainCreateInfoKHR createInfo;
// surface
createInfo.surface = mWindow->getVkSurface();
// old swapchain
createInfo.oldSwapchain = mHandle;
// some defaults
createInfo.imageSharingMode = vk::SharingMode::eExclusive;
createInfo.clipped = VK_TRUE;
createInfo.preTransform = surfaceCapabilities.currentTransform;
// queue families
createInfo.queueFamilyIndexCount = 1;
createInfo.pQueueFamilyIndices = &getOwner()->getDeviceInfo().graphicsQueueFamily;
// present mode and image count
std::tie(createInfo.presentMode, createInfo.minImageCount) = detectPresentMode(*getOwner(), *mWindow, surfaceCapabilities);
// image usage
createInfo.imageUsage = mImageUsage;
MIJIN_ASSERT((surfaceCapabilities.supportedUsageFlags & createInfo.imageUsage) == createInfo.imageUsage, "Invalid image usage flags when creating surface.");
// image format and color space
const vk::SurfaceFormatKHR surfaceFormat = detectImageFormat(*getOwner(), *mWindow, createInfo.imageUsage);
createInfo.imageFormat = surfaceFormat.format;
createInfo.imageColorSpace = surfaceFormat.colorSpace;
// image extent
createInfo.imageExtent = detectImageExtent(*mWindow, surfaceCapabilities);
createInfo.imageArrayLayers = 1;
// composite alpha
createInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque;
MIJIN_ASSERT((surfaceCapabilities.supportedCompositeAlpha & createInfo.compositeAlpha) == createInfo.compositeAlpha, "Invalid composite alpha when creating surface.");
mHandle = getOwner()->getVkHandle().createSwapchainKHR(createInfo);
mFormat = createInfo.imageFormat;
mExtent = createInfo.imageExtent;
//for (ObjectPtr<Image>& image : mImages)
//{
// image->unwrap();
// unregisterImage(&image);
//}
mImages.clear();
// retrieve swapchain images
const std::vector<vk::Image> imageHandles = getOwner()->getVkHandle().getSwapchainImagesKHR(mHandle);
mImages.reserve(imageHandles.size());
// and create image views
// destroyImageViews();
// imageViews.reserve(imageHandles.size());
for (const vk::Image image : imageHandles)
{
mImages.emplace_back(getOwner()->createChild<Image>(ImageWrapArgs{
.handle = image,
.type = vk::ImageType::e2D,
.format = createInfo.imageFormat,
.usage = createInfo.imageUsage,
.size = {
createInfo.imageExtent.width,
createInfo.imageExtent.height,
1
}
}));
// const ImageViewCreateInfo ivCreateInfo =
// {
// .nativeImage = image,
// .viewType = vk::ImageViewType::e2D,
// .format = createInfo.imageFormat,
// .components =
// {
// .r = vk::ComponentSwizzle::eIdentity,
// .g = vk::ComponentSwizzle::eIdentity,
// .b = vk::ComponentSwizzle::eIdentity,
// .a = vk::ComponentSwizzle::eIdentity
// },
// .subresourceRange =
// {
// .aspectMask = vk::ImageAspectFlagBits::eColor,
// .baseMipLevel = 0,
// .levelCount = 1,
// .baseArrayLayer = 0,
// .layerCount = 1
// }
// };
// imageViews.emplace_back().create(ivCreateInfo);
// images.back().setDebugName(fmt::format("Swapchain Image #{}", images.size() - 1));
// registerImage(&images.back());
}
// create "image available" semaphores
for (ObjectPtr<Semaphore>& semaphore : mImageAvailableSemaphores)
{
semaphore = getOwner()->createChild<Semaphore>();
}
if (!firstCreate)
{
getOwner()->getVkHandle().destroySwapchainKHR(createInfo.oldSwapchain);
}
// update size
// imageSize.width = createInfo.imageExtent.width;
// imageSize.height = createInfo.imageExtent.height;
// imageFormat = createInfo.imageFormat;
// already request the first frame
acquireImage();
recreated.emit();
}
void Swapchain::acquireImage()
{
MIJIN_ASSERT(mCurrentImageIdx == INVALID_IMAGE_INDEX, "Attempting to acquire a new image before presenting.");
try
{
const vk::ResultValue<std::uint32_t> result = getOwner()->getVkHandle().acquireNextImageKHR(mHandle, std::numeric_limits<std::uint64_t>::max(), *mImageAvailableSemaphores[mCurrentFrameIdx]);
if (result.result != vk::Result::eSuccess)
{
assert(result.result == vk::Result::eSuboptimalKHR);
recreate();
return;
}
mCurrentImageIdx = result.value;
mImages[mCurrentImageIdx]->resetUsage(ResetLayout::YES);
}
catch(vk::OutOfDateKHRError&) {
recreate();
}
}
mijin::Task<> Swapchain::c_acquireImage()
{
try
{
vk::ResultValue<std::uint32_t> result = vk::ResultValue<std::uint32_t>(vk::Result::eTimeout, 0);
while(true)
{
result = getOwner()->getVkHandle().acquireNextImageKHR(mHandle, 0, *mImageAvailableSemaphores[mCurrentFrameIdx]);
if (result.result != vk::Result::eTimeout && result.result != vk::Result::eNotReady) {
break;
}
co_await mijin::c_suspend();
}
if (result.result != vk::Result::eSuccess)
{
MIJIN_ASSERT(result.result == vk::Result::eSuboptimalKHR, "Error acquiring swapchain image.");
recreate();
co_return;
}
mCurrentImageIdx = result.value;
mImages[mCurrentImageIdx]->resetUsage(ResetLayout::YES);
}
catch(vk::OutOfDateKHRError&) {
recreate();
}
}
}

47
source/texture.cpp Normal file
View File

@@ -0,0 +1,47 @@
#include "iwa/texture.hpp"
#include "iwa/device.hpp"
#include "iwa/resource/bitmap.hpp"
namespace iwa
{
Texture::Texture(TextureCreationArgs args) : super_t(nullptr), mImage(std::move(args.image)),
mImageView(mImage->createImageView(args.imageViewArgs)), mSampler(mImage->getOwner()->createChild<Sampler>(args.samplerArgs))
{
}
mijin::Task<ObjectPtr<Texture>> Texture::c_createSingleColor(const ObjectPtr<Device>& device, const SingleColorTextureArgs& args)
{
ObjectPtr<Bitmap> bitmap = Bitmap::create(BitmapCreationArgs{
.format = args.format,
.size = {.width = 1, .height = 1}
});
bitmap->fill(args.color);
co_return co_await c_createFromBitmap(device, {.bitmap = *bitmap});
}
mijin::Task<ObjectPtr<Texture>> Texture::c_createFromBitmap(const ObjectPtr<Device>& device, const TextureFromBitmapArgs& args)
{
ImageCreationArgs imageCreationArgs = args.imageArgs;
imageCreationArgs.format = args.bitmap.getFormat();
imageCreationArgs.extent.width = args.bitmap.getSize().width;
imageCreationArgs.extent.height = args.bitmap.getSize().height;
imageCreationArgs.extent.depth = 1;
imageCreationArgs.usage |= vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst;
ObjectPtr<Image> image = device->createChild<Image>(imageCreationArgs);
image->allocateMemory();
co_await image->c_upload(args.bitmap);
co_await image->c_doTransition({
.stages = vk::PipelineStageFlagBits::eFragmentShader,
.layout = vk::ImageLayout::eShaderReadOnlyOptimal,
.access = vk::AccessFlagBits::eShaderRead
});
co_return create(TextureCreationArgs{
.image = std::move(image),
.imageViewArgs = args.imageViewArgs,
.samplerArgs = args.samplerArgs
});
}
} // namespace iwa

View File

@@ -0,0 +1,548 @@
#include "iwa/util/glsl_compiler.hpp"
#include <filesystem>
#include <utility>
#include <glslang/Include/InfoSink.h>
#include <glslang/Public/ShaderLang.h>
#include <glslang/MachineIndependent/iomapper.h>
#include <glslang/MachineIndependent/localintermediate.h>
#include <glslang/Public/ResourceLimits.h>
#include <glslang/SPIRV/GlslangToSpv.h>
#include <yaml-cpp/yaml.h>
#include "iwa/device.hpp"
#include "iwa/instance.hpp"
#include "iwa/log.hpp"
#include "iwa/util/dir_stack_file_includer.hpp"
#include "iwa/util/reflect_glsl.hpp"
namespace fs = std::filesystem;
namespace iwa
{
namespace
{
class CustomFileIncluder : public impl::DirStackFileIncluder
{
private:
fs::path workingDir;
mijin::FileSystemAdapter& mFsAdapter;
public:
explicit CustomFileIncluder(mijin::FileSystemAdapter& fsAdapter) noexcept: mFsAdapter(fsAdapter)
{}
public:
void setWorkingDir(const fs::path& workingDir_)
{ workingDir = workingDir_; }
protected: // overrides
IncludeResult* readLocalPath(const char* headerName, const char* includerName, int depth) override
{
// Discard popped include directories, and
// initialize when at parse-time first level.
directoryStack.resize(depth + externalLocalDirectoryCount);
if (depth == 1)
{
directoryStack.back() = getDirectory(includerName);
}
// Find a directory that works, using a reverse search of the include stack.
for (auto it = directoryStack.rbegin(); it != directoryStack.rend(); ++it)
{
std::string path = *it + '/' + headerName;
std::replace(path.begin(), path.end(), '\\', '/');
std::unique_ptr<mijin::Stream> stream;
mijin::StreamError error = mijin::StreamError::UNKNOWN_ERROR;
if (workingDir != fs::path())
{
// try relative include first
error = mFsAdapter.open(workingDir / path, mijin::FileOpenMode::READ, stream);
}
if (error != mijin::StreamError::SUCCESS)
{
error = mFsAdapter.open(path, mijin::FileOpenMode::READ, stream);
}
if (error == mijin::StreamError::SUCCESS)
{
directoryStack.push_back(getDirectory(path));
includedFiles.insert(path);
return newCustomIncludeResult(path, *stream);
}
}
return nullptr;
}
// Do actual reading of the file, filling in a new include result.
IncludeResult* newCustomIncludeResult(const std::string& path, mijin::Stream& stream) const
{
(void) stream.seek(0, mijin::SeekMode::RELATIVE_TO_END);
const std::size_t length = stream.tell();
(void) stream.seek(0);
char* content = new tUserDataElement[length]; // NOLINT(cppcoreguidelines-owning-memory)
const mijin::StreamError error = stream.readRaw(content, length);
if (error != mijin::StreamError::SUCCESS)
{
logAndDie("Error reading include file.");
}
return new IncludeResult(path, content, length, content); // NOLINT(cppcoreguidelines-owning-memory)
}
};
class SemanticIoResolver : public glslang::TDefaultIoResolverBase
{
private:
const std::vector<GLSLSemanticMapping>& mMappings;
public:
SemanticIoResolver(const glslang::TIntermediate& intermediate, const std::vector<GLSLSemanticMapping>& mappings)
: glslang::TDefaultIoResolverBase(intermediate), mMappings(mappings) {}
bool validateBinding(EShLanguage /* stage */, glslang::TVarEntryInfo& /* ent */) override { return true; }
glslang::TResourceType getResourceType(const glslang::TType& type) override {
if (isImageType(type)) {
return glslang::EResImage;
}
if (isTextureType(type)) {
return glslang::EResTexture;
}
if (isSsboType(type)) {
return glslang::EResSsbo;
}
if (isSamplerType(type)) {
return glslang::EResSampler;
}
if (isUboType(type)) {
return glslang::EResUbo;
}
return glslang::EResCount;
}
int resolveBinding(EShLanguage stage, glslang::TVarEntryInfo& ent) override
{
const glslang::TType& type = ent.symbol->getType();
if (type.getQualifier().hasSemantic())
{
const unsigned semantic = type.getQualifier().layoutSemantic;
const unsigned semanticIdx = type.getQualifier().hasSemanticIndex() ? type.getQualifier().layoutSemanticIndex : 0;
auto it = std::ranges::find_if(mMappings, [&](const GLSLSemanticMapping& mapping)
{
return mapping.semantic == semantic && mapping.semanticIdx == semanticIdx;
});
if (it != mMappings.end()) {
return ent.newBinding = it->newBinding;
}
}
// default implementation
const int set = getLayoutSet(type);
// On OpenGL arrays of opaque types take a seperate binding for each element
const int numBindings = referenceIntermediate.getSpv().openGl != 0 && type.isSizedArray() ? type.getCumulativeArraySize() : 1;
const glslang::TResourceType resource = getResourceType(type);
if (resource < glslang::EResCount) {
if (type.getQualifier().hasBinding()) {
return ent.newBinding = reserveSlot(
set, getBaseBinding(stage, resource, set) + type.getQualifier().layoutBinding, numBindings);
}
if (ent.live && doAutoBindingMapping()) {
// find free slot, the caller did make sure it passes all vars with binding
// first and now all are passed that do not have a binding and needs one
return ent.newBinding = getFreeSlot(set, getBaseBinding(stage, resource, set), numBindings);
}
}
return ent.newBinding = -1;
}
int resolveSet(EShLanguage stage, glslang::TVarEntryInfo& ent) override
{
const glslang::TType& type = ent.symbol->getType();
if (type.getQualifier().hasSemantic())
{
const unsigned semantic = type.getQualifier().layoutSemantic;
const unsigned semanticIdx = type.getQualifier().hasSemanticIndex() ? type.getQualifier().layoutSemanticIndex : 0;
auto it = std::ranges::find_if(mMappings, [&](const GLSLSemanticMapping& mapping)
{
return mapping.semantic == semantic && mapping.semanticIdx == semanticIdx;
});
if (it == mMappings.end()) {
return glslang::TDefaultIoResolverBase::resolveSet(stage, ent);
}
return ent.newSet = it->newSet;
}
return glslang::TDefaultIoResolverBase::resolveSet(stage, ent);
}
void addStage(EShLanguage stage, glslang::TIntermediate& stageIntermediate) override
{
nextInputLocation = nextOutputLocation = 0;
glslang::TDefaultIoResolverBase::addStage(stage, stageIntermediate);
}
};
void initGlslang() noexcept
{
static bool inited = false;
if (inited)
{
return;
}
inited = true;
if (!glslang::InitializeProcess())
{
logAndDie("Error initializing Glslang.");
}
}
} // namespace
EShLanguage typeToGlslang(vk::ShaderStageFlagBits type) noexcept
{
switch (type)
{
case vk::ShaderStageFlagBits::eCompute:
return EShLangCompute;
case vk::ShaderStageFlagBits::eVertex:
return EShLangVertex;
case vk::ShaderStageFlagBits::eFragment:
return EShLangFragment;
case vk::ShaderStageFlagBits::eRaygenKHR:
return EShLangRayGen;
case vk::ShaderStageFlagBits::eClosestHitKHR:
return EShLangClosestHit;
case vk::ShaderStageFlagBits::eAnyHitKHR:
return EShLangAnyHit;
case vk::ShaderStageFlagBits::eMissKHR:
return EShLangMiss;
case vk::ShaderStageFlagBits::eIntersectionKHR:
return EShLangIntersect;
case vk::ShaderStageFlagBits::eCallableKHR:
return EShLangCallable;
case vk::ShaderStageFlagBits::eTaskEXT:
return EShLangTask;
case vk::ShaderStageFlagBits::eMeshEXT:
return EShLangMesh;
case vk::ShaderStageFlagBits::eTessellationControl:
return EShLangTessControl;
case vk::ShaderStageFlagBits::eTessellationEvaluation:
return EShLangTessEvaluation;
case vk::ShaderStageFlagBits::eGeometry:
return EShLangGeometry;
case vk::ShaderStageFlagBits::eAllGraphics:
case vk::ShaderStageFlagBits::eAll:
case vk::ShaderStageFlagBits::eSubpassShadingHUAWEI:
case vk::ShaderStageFlagBits::eClusterCullingHUAWEI:
break; // let it fail
}
logAndDie("Invalid value passed to typeToGlslang!");
}
ShaderSource ShaderSource::fromStream(mijin::Stream& stream, std::string fileName)
{
ShaderSource result = {
.fileName = std::move(fileName)
};
if (const mijin::StreamError error = stream.readAsString(result.code); error != mijin::StreamError::SUCCESS)
{
// TODO: custom exception type, for stacktrace and stuff
throw std::runtime_error("Error reading shader source.");
}
return result;
}
ShaderSource ShaderSource::fromFile(const mijin::PathReference& file)
{
std::unique_ptr<mijin::Stream> stream;
if (const mijin::StreamError error = file.open(mijin::FileOpenMode::READ, stream); error != mijin::StreamError::SUCCESS)
{
throw std::runtime_error("Error opening file for reading shader source.");
}
return fromStream(*stream, file.getPath().string());
}
ShaderSource ShaderSource::fromYaml(const YAML::Node& node, const mijin::PathReference& yamlFile)
{
if (node.Tag() == "!load")
{
return fromFile(yamlFile.getAdapter()->getPath(node.as<std::string>()));
}
const std::string& source = node["source"].as<std::string>();
std::string fileName;
if (const YAML::Node fileNameNode = node["fileName"]; !fileNameNode.IsNull())
{
fileName = fileNameNode.as<std::string>();
}
return {
.code = source,
.fileName = std::move(fileName)
};
}
GLSLShader::GLSLShader(ObjectPtr<Instance> owner, GLSLShaderCreationArgs args) noexcept
: super_t(std::move(owner)), mType(args.type), mSources(std::move(args.sources)), mDefines(std::move(args.defines))
{
MIJIN_ASSERT(!mSources.empty(), "Cannot compile without sources.");
compile();
}
GLSLShader::~GLSLShader() noexcept = default;
std::unique_ptr<glslang::TShader> GLSLShader::releaseHandle()
{
if (mHandle == nullptr) {
compile();
}
return std::exchange(mHandle, nullptr);
}
ShaderMeta GLSLShader::getPartialMeta()
{
if (mHandle == nullptr) {
compile();
}
return reflectShader(*mHandle);
}
void GLSLShader::compile()
{
initGlslang();
const EShLanguage stage = typeToGlslang(mType);
std::unique_ptr<glslang::TShader> shader = std::make_unique<glslang::TShader>(stage); // NOLINT(cppcoreguidelines-owning-memory)
std::vector<const char*> sources;
std::vector<int> lengths;
std::vector<const char*> names;
sources.reserve(mSources.size() + 1);
lengths.reserve(mSources.size() + 1);
names.reserve(mSources.size() + 1);
std::string preamble = getOwner()->getInstanceExtension<GLSLCompilerSettings>().getCommonPreamble();
for (const std::string& define : mDefines) {
preamble.append(fmt::format("\n#define {}\n", define));
}
sources.push_back(preamble.c_str());
lengths.push_back(static_cast<int>(preamble.size()));
names.push_back("<preamble>");
for (const ShaderSource& source : mSources)
{
sources.push_back(source.code.c_str());
lengths.push_back(static_cast<int>(source.code.size()));
names.push_back(source.fileName.c_str());
}
shader->setStringsWithLengthsAndNames(sources.data(), lengths.data(), names.data(), static_cast<int>(sources.size()));
shader->setDebugInfo(true);
shader->setEnvInput(glslang::EShSourceGlsl, stage, glslang::EShClientVulkan, glslang::EShTargetVulkan_1_3);
shader->setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_3);
shader->setEnvTarget(glslang::EShTargetLanguage::EShTargetSpv, glslang::EShTargetSpv_1_6);
shader->setAutoMapLocations(true);
shader->setAutoMapBindings(true);
const EShMessages PREPROCESS_MESSAGES = static_cast<EShMessages>(EShMsgDefault
#if !defined(KAZAN_RELEASE)
| EShMsgDebugInfo
#endif
);
std::string completeCode;
CustomFileIncluder includer(getOwner()->getPrimaryFSAdapter()); // TODO: this type seems to be doing stupid things, investigate
const std::string sourceFileAbsStr = mSources[0].fileName; // just for you MSVC <3
if (!sourceFileAbsStr.empty()) {
includer.setWorkingDir(fs::path(sourceFileAbsStr).parent_path());
}
const bool couldPreprocess = shader->preprocess(
/* builtInResources = */ GetDefaultResources(),
/* defaultVersion = */ 450,
/* defaultProfile = */ ECoreProfile,
/* forceDefaultVersionAndProfile = */ false,
/* forwardCompatible = */ false,
/* message = */ PREPROCESS_MESSAGES,
/* outputString = */ &completeCode,
/* includer = */ includer
);
if (!couldPreprocess)
{
logMsg("GLSL preprocessing failed:\ninfo log:\n{}\ndebug log:\n{}",
shader->getInfoLog(), shader->getInfoDebugLog()
);
logAndDie("Error preprocessing shader.");
}
#if 0
ShaderPreprocessResult preprocessResult = preprocessShader(completeCode);
for (std::string& module : preprocessResult.importedModules) {
importedModules.insert(std::move(module));
}
for (std::string& option : preprocessResult.supportedOptions) {
supportedOptions.insert(std::move(option));
}
#endif
const char* newSource = completeCode.c_str();
#if defined(KAZAN_RELEASE)
shader->setStrings(&newSource, 1); // replace source with the preprocessed one
#else
const int newSourceLen = static_cast<int>(std::strlen(newSource));
const char* newSourceName = sourceFileAbsStr.c_str();
shader->setStringsWithLengthsAndNames(&newSource, &newSourceLen, &newSourceName, 1);
#endif
const EShMessages PARSE_MESSAGES = static_cast<EShMessages>(EShMsgDefault
#if !defined(KAZAN_RELEASE)
| EShMsgDebugInfo
#endif
);
const bool couldParse = shader->parse(
/* builtinResources = */ GetDefaultResources(),
/* defaultVersion = */ 450,
/* forwardCompatible = */ false,
/* messages = */ PARSE_MESSAGES
);
if (!couldParse)
{
logMsg("GLSL parsing failed:\ninfo log:\n{}\ndebug log:\n{}",
shader->getInfoLog(), shader->getInfoDebugLog()
);
logAndDie("Error parsing shader.");
}
mHandle = std::move(shader);
}
GLSLShaderProgram::GLSLShaderProgram(ObjectPtr<Device> owner, GLSLShaderProgramCreationArgs args)
: super_t(std::move(owner)), mLinkFlags(args.linkFlags)
{
MIJIN_ASSERT_FATAL(!args.shaders.empty(), "At least one shader per program is required!");
mHandle = std::make_unique<glslang::TProgram>();
for (const ObjectPtr<GLSLShader>& shader : args.shaders)
{
mShaderHandles.push_back(shader->releaseHandle());
mHandle->addShader(mShaderHandles.back().get());
}
const EShMessages linkMessages = static_cast<EShMessages>(EShMsgSpvRules | EShMsgVulkanRules
| (args.linkFlags.withDebugInfo ? EShMsgDebugInfo : EShMessages(0)));
if (!mHandle->link(linkMessages))
{
logAndDie("GLSL linking failed!\ninfo log:\n{}\ndebug log:\n{}",
mHandle->getInfoLog(), mHandle->getInfoDebugLog()
);
}
glslang::TIntermediate* referenceIntermediate = mHandle->getIntermediate(typeToGlslang(args.shaders[0]->getType()));
SemanticIoResolver ioResolver(*referenceIntermediate, args.semanticMappings);
if (!mHandle->mapIO(&ioResolver))
{
logAndDie("GLSL io mapping failed!\ninfo log:\n{}\ndebug log:\n{}",
mHandle->getInfoLog(), mHandle->getInfoDebugLog()
);
}
mMeta = reflectProgram(*mHandle);
}
std::vector<std::uint32_t> GLSLShaderProgram::generateSpirv(vk::ShaderStageFlagBits stage) const
{
const EShLanguage glslLang = typeToGlslang(stage);
glslang::SpvOptions options = {};
if (mLinkFlags.withDebugInfo)
{
options.generateDebugInfo = true;
options.stripDebugInfo = false;
options.disableOptimizer = true;
options.emitNonSemanticShaderDebugInfo = true;
options.emitNonSemanticShaderDebugSource = false; // TODO: this should be true, but makes GLSLang crash
}
else
{
options.generateDebugInfo = false;
options.stripDebugInfo = true;
options.disableOptimizer = false;
options.emitNonSemanticShaderDebugInfo = true; // TODO: this should be false, but that also crashes GLSLang ...
options.emitNonSemanticShaderDebugSource = false;
}
options.optimizeSize = false;
options.disassemble = false;
options.validate = true;
spv::SpvBuildLogger logger;
const glslang::TIntermediate* intermediate = mHandle->getIntermediate(glslLang);
if (intermediate == nullptr)
{
throw std::runtime_error("Attempting to generate SpirV from invalid shader stage.");
}
std::vector<std::uint32_t> spirv;
glslang::GlslangToSpv(*intermediate, spirv, &logger, &options);
const std::string messages = logger.getAllMessages();
if (!messages.empty())
{
logMsg("SpirV messages: {}", messages);
}
return spirv;
}
std::vector<PipelineStage> GLSLShaderProgram::generatePipelineStages() const
{
std::vector<PipelineStage> stages;
for (const vk::ShaderStageFlagBits stage : mMeta.stages)
{
const std::vector<std::uint32_t> spirv = generateSpirv(stage);
stages.push_back({
.shader = getOwner()->createChild<ShaderModule>(ShaderModuleCreationArgs{.code = spirv}),
.stage = stage
});
}
return stages;
}
GraphicsPipelineCreationArgs GLSLShaderProgram::prepareGraphicsPipeline(PrepareGraphicsPipelineArgs& args) const
{
args.pipelineLayoutMeta = mMeta.generatePipelineLayout(args.layoutArgs);
args.layouts = args.pipelineLayoutMeta.createPipelineLayout(*getOwner());
return {
.stages = generatePipelineStages(),
.vertexInput = mMeta.generateVertexInputFromLayout(args.vertexLayout),
.layout = args.layouts.pipelineLayout
};
}
ComputePipelineCreationArgs GLSLShaderProgram::prepareComputePipeline(PrepareComputePipelineArgs& args) const
{
args.pipelineLayoutMeta = mMeta.generatePipelineLayout(args.layoutArgs);
args.layouts = args.pipelineLayoutMeta.createPipelineLayout(*getOwner());
return {
.stage = generatePipelineStages()[0],
.layout = args.layouts.pipelineLayout
};
}
// GraphicsPipelineCreationArgs prepareGLSLGraphicsPipeline(const PrepareGLSLGraphicsPipelineArgs& args)
// {
// return {
// .stages =
// {
// PipelineStage{.shader = vertexShaderModule, .stage = vk::ShaderStageFlagBits::eVertex},
// PipelineStage{.shader = fragmentShaderModule, .stage = vk::ShaderStageFlagBits::eFragment}
// },
// .vertexInput = std::move(vertexInput),
// .inputAssembly =
// {
// .topology = vk::PrimitiveTopology::eTriangleStrip
// },
// .colorBlend =
// {
// .attachements = {DEFAULT_BLEND_ATTACHMENT}
// },
// .renderingInfo = GraphicsPipelineRenderingInfo{
// .colorAttachmentFormats = {mRenderTarget->getFormat()}
// },
// .layout = mPipelineLayout
// };
// }
} // namespace iwa

View File

@@ -0,0 +1,33 @@
#include "iwa/util/growing_descriptor_pool.hpp"
#include <utility>
#include "iwa/device.hpp"
namespace iwa
{
GrowingDescriptorPool::GrowingDescriptorPool(ObjectPtr<Device> owner, GrowingDescriptorPoolCreationArgs args)
: super_t(std::move(owner)), mCreationArgs(std::move(args))
{
}
ObjectPtr<DescriptorSet> GrowingDescriptorPool::allocateDescriptorSet(const DescriptorSetAllocateArgs& args)
{
for (const ObjectPtr<DescriptorPool>& pool : mPools)
{
try
{
return pool->allocateDescriptorSet(args);
}
catch (vk::FragmentedPoolError&) {}
catch (vk::OutOfPoolMemoryError&) {}
// any other error shall be forwarded
}
// couldn't allocate in any of the existing pools, create a new one
mPools.push_back(getOwner()->createChild<DescriptorPool>(mCreationArgs));
return mPools.back()->allocateDescriptorSet(args); // raise any error that may occur
}
} // namespace iwa

View File

@@ -0,0 +1,147 @@
#include "iwa/util/image_reference.hpp"
#include <mijin/util/iterators.hpp>
#include "iwa/command.hpp"
#include "iwa/device.hpp"
namespace iwa
{
ImageReference::ImageReference(ObjectPtr<Device> owner) : super_t(std::move(owner))
{
}
void ImageReference::finalize(ImageReferenceFinalizeArgs& /* args */) {}
mijin::Task<> ImageReference::c_present()
{
co_return;
}
SwapchainImageReference::SwapchainImageReference(ObjectPtr<Device> owner, SwapchainImageReferenceCreationArgs args)
: super_t(std::move(owner)), mSwapchain(std::move(args.swapchain))
{
mPresentReadySemaphores.resize(mSwapchain->getNumParallelFrames());
for (ObjectPtr<Semaphore>& semaphore : mPresentReadySemaphores)
{
semaphore = getOwner()->createChild<Semaphore>();
}
createImageViews();
mSwapchain->recreated.connect([this]()
{
createImageViews();
});
}
vk::Format SwapchainImageReference::getFormat()
{
return mSwapchain->getFormat();
}
vk::Extent2D SwapchainImageReference::getExtent()
{
return mSwapchain->getExtent();
}
ImageReferenceFrame SwapchainImageReference::getCurrentFrame()
{
return ImageReferenceFrame{
.image = mSwapchain->getCurrentImage().getRaw(),
.imageView = mImageViews[mSwapchain->getCurrentImageIdx()].getRaw()
};
}
void SwapchainImageReference::finalize(ImageReferenceFinalizeArgs& args)
{
args.waitSemaphores.push_back(*mSwapchain->getCurrentAvailableSemaphore());
args.signalSemaphores.push_back(*mPresentReadySemaphores[mSwapchain->getCurrentFrameIdx()]);
mSwapchain->getCurrentImage()->applyTransition(args.cmdBuffer, ImageTransition{
.stages = vk::PipelineStageFlagBits::eBottomOfPipe,
.layout = vk::ImageLayout::ePresentSrcKHR,
.access = {}
});
}
mijin::Task<> SwapchainImageReference::c_present()
{
// and present
co_await mSwapchain->c_present({
.queue = getOwner()->getGraphicsQueue(),
.waitSemaphores = {mPresentReadySemaphores[mSwapchain->getCurrentFrameIdx()]->getVkHandle()}
});
}
void SwapchainImageReference::createImageViews()
{
mImageViews.resize(mSwapchain->getImages().size());
for (auto [image, imageView] : mijin::zip(mSwapchain->getImages(), mImageViews))
{
imageView = image->createImageView();
}
}
DirectImageReference::DirectImageReference(ObjectPtr<Device> owner, DirectImageReferenceCreationArgs args)
: super_t(std::move(owner)), mImage(std::move(args.image)), mImageView(std::move(args.imageView))
{
}
vk::Format DirectImageReference::getFormat()
{
return mImage->getFormat();
}
vk::Extent2D DirectImageReference::getExtent()
{
return {
.width = mImage->getSize().width,
.height = mImage->getSize().height
};
}
ImageReferenceFrame DirectImageReference::getCurrentFrame()
{
return ImageReferenceFrame{
.image = mImage.getRaw(),
.imageView = mImageView.getRaw()
};
}
AutoResizeImageReference::AutoResizeImageReference(ObjectPtr<Device> owner, AutoResizeImageReferenceCreationArgs args)
: super_t(std::move(owner), DirectImageReferenceCreationArgs{}), mReferenceImageRef(std::move(args.referenceImageRef)),
mImageCreationArgs(std::move(args.imageCreationArgs)), mImageViewCreationArgs(args.imageViewCreationArgs)
{
createImage();
}
vk::Extent2D AutoResizeImageReference::getExtent()
{
return mReferenceImageRef->getExtent();
}
ImageReferenceFrame AutoResizeImageReference::getCurrentFrame()
{
const vk::Extent2D extent = mReferenceImageRef->getExtent();
if (extent.width != mImage->getSize().width || extent.height != mImage->getSize().height) {
createImage();
}
return ImageReferenceFrame{
.image = mImage.getRaw(),
.imageView = mImageView.getRaw()
};
}
void AutoResizeImageReference::createImage()
{
const vk::Extent2D extent = mReferenceImageRef->getExtent();
mImageCreationArgs.extent.width = extent.width;
mImageCreationArgs.extent.height = extent.height;
mImageCreationArgs.extent.depth = 1;
mImage = getOwner()->createChild<Image>(mImageCreationArgs);
mImage->allocateMemory();
mImageView = mImage->createImageView(mImageViewCreationArgs);
}
} // namespace iwa

View File

@@ -0,0 +1,587 @@
#include "iwa/util/reflect_glsl.hpp"
#include <glslang/Include/InfoSink.h>
#include <glslang/Public/ShaderLang.h>
#include <glslang/MachineIndependent/localintermediate.h>
#include <glslang/Public/ResourceLimits.h>
namespace iwa
{
namespace
{
class MetaCollectingTraverser : public glslang::TIntermTraverser
{
private:
ShaderMeta& meta;
vk::ShaderStageFlagBits shaderType;
public:
inline MetaCollectingTraverser(ShaderMeta& meta_, vk::ShaderStageFlagBits shaderType_) : meta(meta_), shaderType(shaderType_)
{}
bool visitBinary(glslang::TVisit, glslang::TIntermBinary* node) override;
bool visitUnary(glslang::TVisit, glslang::TIntermUnary* node) override;
bool visitAggregate(glslang::TVisit, glslang::TIntermAggregate* node) override;
bool visitSelection(glslang::TVisit, glslang::TIntermSelection* node) override;
void visitConstantUnion(glslang::TIntermConstantUnion* node) override;
void visitSymbol(glslang::TIntermSymbol* node) override;
bool visitLoop(glslang::TVisit, glslang::TIntermLoop* node) override;
bool visitBranch(glslang::TVisit, glslang::TIntermBranch* node) override;
bool visitSwitch(glslang::TVisit, glslang::TIntermSwitch* node) override;
};
vk::Format convertGlslangBaseType(const glslang::TType& type)
{
switch (type.getBasicType())
{
case glslang::EbtInt:
return vk::Format::eR32Sint;
case glslang::EbtUint:
return vk::Format::eR32Uint;
case glslang::EbtFloat:
return vk::Format::eR32Sfloat;
case glslang::EbtDouble:
return vk::Format::eR64Sfloat;
default:
break;
}
logAndDie("Don't know how to convert Glslang basic type :*(");
}
vk::Format convertGlslangLayoutFormat(glslang::TLayoutFormat layoutFormat)
{
switch (layoutFormat)
{
case glslang::TLayoutFormat::ElfNone:
return vk::Format::eUndefined;
// Float image
case glslang::TLayoutFormat::ElfRgba32f:
return vk::Format::eR32G32B32A32Sfloat;
case glslang::TLayoutFormat::ElfRgba16f:
return vk::Format::eR16G16B16A16Sfloat;
case glslang::TLayoutFormat::ElfR32f:
return vk::Format::eR32Sfloat;
case glslang::TLayoutFormat::ElfRgba8:
return vk::Format::eR8G8B8A8Unorm;
case glslang::TLayoutFormat::ElfRgba8Snorm:
return vk::Format::eR8G8B8A8Snorm;
case glslang::TLayoutFormat::ElfRg32f:
return vk::Format::eR32G32Sfloat;
case glslang::TLayoutFormat::ElfRg16f:
return vk::Format::eR16G16Sfloat;
case glslang::TLayoutFormat::ElfR11fG11fB10f:
return vk::Format::eB10G11R11UfloatPack32; // TODO: ?
case glslang::TLayoutFormat::ElfR16f:
return vk::Format::eR16Sfloat;
case glslang::TLayoutFormat::ElfRgba16:
return vk::Format::eR16G16B16A16Unorm;
case glslang::TLayoutFormat::ElfRgb10A2:
return vk::Format::eA2R10G10B10SnormPack32; // TODO: ?
case glslang::TLayoutFormat::ElfRg16:
return vk::Format::eR16G16Unorm;
case glslang::TLayoutFormat::ElfRg8:
return vk::Format::eR8G8Unorm;
case glslang::TLayoutFormat::ElfR16:
return vk::Format::eR16Unorm;
case glslang::TLayoutFormat::ElfR8:
return vk::Format::eR8Unorm;
case glslang::TLayoutFormat::ElfRgba16Snorm:
return vk::Format::eR16G16B16A16Snorm;
case glslang::TLayoutFormat::ElfRg16Snorm:
return vk::Format::eR16G16Unorm;
case glslang::TLayoutFormat::ElfRg8Snorm:
return vk::Format::eR8G8Snorm;
case glslang::TLayoutFormat::ElfR16Snorm:
return vk::Format::eR16G16Snorm;
case glslang::TLayoutFormat::ElfR8Snorm:
return vk::Format::eR8Snorm;
// Int image
case glslang::TLayoutFormat::ElfRgba32i:
return vk::Format::eR32G32B32A32Sint;
case glslang::TLayoutFormat::ElfRgba16i:
return vk::Format::eR16G16B16A16Sint;
case glslang::TLayoutFormat::ElfRgba8i:
return vk::Format::eR8G8B8A8Sint;
case glslang::TLayoutFormat::ElfR32i:
return vk::Format::eR32Sint;
case glslang::TLayoutFormat::ElfRg32i:
return vk::Format::eR32G32Sint;
case glslang::TLayoutFormat::ElfRg16i:
return vk::Format::eR16G16Sint;
case glslang::TLayoutFormat::ElfRg8i:
return vk::Format::eR8G8Sint;
case glslang::TLayoutFormat::ElfR16i:
return vk::Format::eR16Sint;
case glslang::TLayoutFormat::ElfR8i:
return vk::Format::eR8Sint;
case glslang::TLayoutFormat::ElfR64i:
return vk::Format::eR64Sint;
// Uint image
case glslang::TLayoutFormat::ElfRgba32ui:
return vk::Format::eR32G32B32A32Uint;
case glslang::TLayoutFormat::ElfRgba16ui:
return vk::Format::eR16G16B16A16Uint;
case glslang::TLayoutFormat::ElfRgba8ui:
return vk::Format::eR8G8B8A8Uint;
case glslang::TLayoutFormat::ElfR32ui:
return vk::Format::eR32Uint;
case glslang::TLayoutFormat::ElfRg32ui:
return vk::Format::eR32G32Uint;
case glslang::TLayoutFormat::ElfRg16ui:
return vk::Format::eR16G16Uint;
case glslang::TLayoutFormat::ElfRgb10a2ui:
return vk::Format::eA2R10G10B10UintPack32;
case glslang::TLayoutFormat::ElfRg8ui:
return vk::Format::eR8G8Uint;
case glslang::TLayoutFormat::ElfR16ui:
return vk::Format::eR16Uint;
case glslang::TLayoutFormat::ElfR8ui:
return vk::Format::eR8Uint;
case glslang::TLayoutFormat::ElfR64ui:
return vk::Format::eR64Uint;
// other/unknown
case glslang::TLayoutFormat::ElfSize1x8:
case glslang::TLayoutFormat::ElfSize1x16:
case glslang::TLayoutFormat::ElfSize1x32:
case glslang::TLayoutFormat::ElfSize2x32:
case glslang::TLayoutFormat::ElfSize4x32:
case glslang::TLayoutFormat::ElfEsFloatGuard:
case glslang::TLayoutFormat::ElfFloatGuard:
case glslang::TLayoutFormat::ElfEsIntGuard:
case glslang::TLayoutFormat::ElfIntGuard:
case glslang::TLayoutFormat::ElfEsUintGuard:
case glslang::TLayoutFormat::ElfExtSizeGuard:
case glslang::TLayoutFormat::ElfCount:
break;
}
logAndDie("Unexpected format in convertGlslangLayoutFormat()."); // : {}", layoutFormat);
}
vk::Format convertGlslangVectorType(glslang::TBasicType basicType, int vectorSize)
{
switch (basicType)
{
case glslang::EbtFloat:
switch (vectorSize)
{
case 2:
return vk::Format::eR32G32Sfloat;
case 3:
return vk::Format::eR32G32B32Sfloat;
case 4:
return vk::Format::eR32G32B32A32Sfloat;
default:
break;
}
break;
case glslang::EbtDouble:
switch (vectorSize)
{
case 2:
return vk::Format::eR64G64Sfloat;
case 3:
return vk::Format::eR64G64B64Sfloat;
case 4:
return vk::Format::eR64G64B64A64Sfloat;
default:
break;
}
break;
case glslang::EbtInt:
switch (vectorSize)
{
case 2:
return vk::Format::eR32G32Sint;
case 3:
return vk::Format::eR32G32B32Sint;
case 4:
return vk::Format::eR32G32B32A32Sint;
default:
break;
}
break;
case glslang::EbtUint:
switch (vectorSize)
{
case 2:
return vk::Format::eR32G32Uint;
case 3:
return vk::Format::eR32G32B32Uint;
case 4:
return vk::Format::eR32G32B32A32Uint;
default:
break;
}
break;
case glslang::EbtBool: // NOLINT(bugprone-branch-clone) TODO: ???
break;
default:
break;
}
logAndDie("Don't know how to convert Glslang vector type :(");
}
vk::Format convertGlslangVectorType(const glslang::TType& type)
{
assert(type.isVector());
return convertGlslangVectorType(type.getBasicType(), type.getVectorSize());
}
ShaderVariableMatrixType convertGlslangMatrixType(const glslang::TType& type)
{
assert(type.isMatrix());
assert(type.getMatrixCols() == type.getMatrixRows()); // only supported types yet...
switch (type.getMatrixCols())
{
case 2:
return ShaderVariableMatrixType::MAT2;
case 3:
return ShaderVariableMatrixType::MAT3;
case 4:
return ShaderVariableMatrixType::MAT4;
default:
break;
}
logAndDie("Don't know how to convert Glslang matrix type -.-");
}
ImageDim convertGlslangSamplerDim(glslang::TSamplerDim dim)
{
switch (dim)
{
case glslang::TSamplerDim::Esd1D:
return ImageDim::ONE;
case glslang::TSamplerDim::Esd2D:
return ImageDim::TWO;
case glslang::TSamplerDim::Esd3D:
return ImageDim::THREE;
case glslang::TSamplerDim::EsdCube:
return ImageDim::CUBE;
default:
break;
}
logAndDie("Don't know how to convert Glslang sampler dimensions ...");
}
ShaderVariableType convertGlslangType(const glslang::TType& type)
{
ShaderVariableType result;
if (type.isVector())
{
result.baseType = ShaderVariableBaseType::SIMPLE;
result.simple.format = convertGlslangVectorType(type);
} else if (type.isMatrix())
{
result.baseType = ShaderVariableBaseType::MATRIX;
result.matrixType = convertGlslangMatrixType(type);
} else if (type.isStruct())
{
const std::size_t numMembers = type.getStruct()->size();
result.baseType = ShaderVariableBaseType::STRUCT;
result.struct_.members.reserve(numMembers);
std::size_t currentOffset = 0;
for (const glslang::TTypeLoc& typeLoc: *type.getStruct())
{
ShaderVariableStructMember& member = result.struct_.members.emplace_back();
member.name = typeLoc.type->getFieldName();
member.type = convertGlslangType(*typeLoc.type);
member.offset = currentOffset;
if (typeLoc.type->getQualifier().hasSemantic())
{
member.semantic = typeLoc.type->getQualifier().layoutSemantic;
}
if (typeLoc.type->getQualifier().hasSemanticIndex())
{
member.semanticIdx = typeLoc.type->getQualifier().layoutSemanticIndex;
}
currentOffset = member.offset + calcShaderTypeSize(member.type); // TODO: padding
}
} else if (type.getBasicType() == glslang::EbtSampler)
{
const glslang::TSampler& sampler = type.getSampler();
result.baseType = ShaderVariableBaseType::IMAGE;
result.image.dimensions = convertGlslangSamplerDim(sampler.dim);
result.image.format = convertGlslangLayoutFormat(type.getQualifier().layoutFormat);
} else
{
result.baseType = ShaderVariableBaseType::SIMPLE;
result.simple.format = convertGlslangBaseType(type);
}
if (type.isArray())
{
if (type.isArrayVariablyIndexed())
{
result.arraySize = 0;
result.dynamicArraySize = true;
} else
{
assert(type.getArraySizes()->getNumDims() == 1); // don't support multi dimensional arrays yet
result.arraySize = type.getOuterArraySize();
}
}
return result;
}
vk::DescriptorType getGlslangDescriptorType(const glslang::TType& type)
{
if (type.getBasicType() == glslang::EbtSampler)
{
if (type.getSampler().combined)
{
return vk::DescriptorType::eCombinedImageSampler;
}
if (type.getSampler().isImage())
{
return vk::DescriptorType::eStorageImage;
}
} else if (type.isStruct())
{
if (type.getQualifier().isUniform())
{
return vk::DescriptorType::eUniformBuffer;
}
return vk::DescriptorType::eStorageBuffer;
}
logAndDie("No idea what to do with this type :/");
}
bool MetaCollectingTraverser::visitBinary(glslang::TVisit, glslang::TIntermBinary* node)
{
(void) node;
return false;
}
bool MetaCollectingTraverser::visitUnary(glslang::TVisit, glslang::TIntermUnary* node)
{
(void) node;
return false;
}
bool MetaCollectingTraverser::visitAggregate(glslang::TVisit, glslang::TIntermAggregate* node)
{
switch (node->getOp())
{
case glslang::EOpSequence:
return true;
case glslang::EOpFunction:
break;
case glslang::EOpLinkerObjects:
return true;
default:
break;
}
return false;
}
bool MetaCollectingTraverser::visitSelection(glslang::TVisit, glslang::TIntermSelection* node)
{
(void) node;
return false;
}
void MetaCollectingTraverser::visitConstantUnion(glslang::TIntermConstantUnion* node)
{
(void) node;
}
void MetaCollectingTraverser::visitSymbol(glslang::TIntermSymbol* node)
{
const bool isLinkerObject = getParentNode()
&& getParentNode()->getAsAggregate()
&& getParentNode()->getAsAggregate()->getOp() == glslang::EOpLinkerObjects;
if (isLinkerObject)
{
if (node->getQualifier().builtIn)
{
return;
}
if (node->getQualifier().isUniformOrBuffer())
{
if (node->getQualifier().isPushConstant())
{
ShaderPushConstantBlock pushConstantBlock;
pushConstantBlock.type = convertGlslangType(node->getType());
assert(pushConstantBlock.type.baseType == ShaderVariableBaseType::STRUCT);
meta.extendPushConstant(pushConstantBlock, ShaderTypeBits::make(shaderType));
return;
}
const unsigned setIdx = node->getQualifier().hasSet() ? node->getQualifier().layoutSet : UNSPECIFIED_INDEX;
const unsigned binding = node->getQualifier().hasBinding() ? node->getQualifier().layoutBinding : UNSPECIFIED_INDEX;
ShaderVariableSet& set = meta.getOrCreateInterfaceVariableSet(setIdx);
assert(setIdx == UNSPECIFIED_INDEX || !set.getVariableAtBindingOpt(binding)); // multiple bindings at the same index?
set.usedInStages.set(shaderType, true);
ShaderVariable& var = set.variables.emplace_back();
var.binding = binding;
var.name = node->getName();
if (node->getQualifier().hasSemantic())
{
var.semantic = node->getQualifier().layoutSemantic;
}
if (node->getQualifier().hasSemanticIndex())
{
var.semanticIndex = node->getQualifier().layoutSemanticIndex;
}
// uniform blocks are identified by the name of their type
if (var.name.empty() || var.name.starts_with("anon@"))
{
const glslang::TString& typeName = node->getType().getTypeName();
if (!typeName.empty())
{
var.name = typeName;
}
}
var.descriptorType = getGlslangDescriptorType(node->getType());
var.type = convertGlslangType(node->getType());
}
else if (node->getQualifier().storage == glslang::EvqVaryingIn)
{
ShaderAttribute attribute;
attribute.stage = shaderType;
attribute.type = convertGlslangType(node->getType());
attribute.location = node->getQualifier().hasLocation() ? node->getQualifier().layoutLocation : UNSPECIFIED_INDEX;
attribute.name = node->getName();
if (node->getQualifier().hasSemantic())
{
attribute.semantic = node->getQualifier().layoutSemantic;
}
if (node->getQualifier().hasSemanticIndex())
{
attribute.semanticIndex = node->getQualifier().layoutSemanticIndex;
}
meta.addInputAttribute(std::move(attribute));
}
else if (node->getQualifier().storage == glslang::EvqVaryingOut)
{
ShaderAttribute attribute;
attribute.stage = shaderType;
attribute.type = convertGlslangType(node->getType());
attribute.location = node->getQualifier().hasLocation() ? node->getQualifier().layoutLocation : UNSPECIFIED_INDEX;
attribute.name = node->getName();
if (node->getQualifier().hasSemantic())
{
attribute.semantic = node->getQualifier().layoutSemantic;
}
if (node->getQualifier().hasSemanticIndex())
{
attribute.semanticIndex = node->getQualifier().layoutSemanticIndex;
}
meta.addOutputAttribute(std::move(attribute));
}
}
}
bool MetaCollectingTraverser::visitLoop(glslang::TVisit, glslang::TIntermLoop* node)
{
(void) node;
return false;
}
bool MetaCollectingTraverser::visitBranch(glslang::TVisit, glslang::TIntermBranch* node)
{
(void) node;
return false;
}
bool MetaCollectingTraverser::visitSwitch(glslang::TVisit, glslang::TIntermSwitch* node)
{
(void) node;
return false;
}
vk::ShaderStageFlagBits shaderStageFromGlslang(EShLanguage language)
{
switch (language)
{
case EShLangVertex:
return vk::ShaderStageFlagBits::eVertex;
case EShLangTessControl:
return vk::ShaderStageFlagBits::eTessellationControl;
case EShLangTessEvaluation:
return vk::ShaderStageFlagBits::eTessellationEvaluation;
case EShLangGeometry:
return vk::ShaderStageFlagBits::eGeometry;
case EShLangFragment:
return vk::ShaderStageFlagBits::eFragment;
case EShLangCompute:
return vk::ShaderStageFlagBits::eCompute;
case EShLangRayGen:
return vk::ShaderStageFlagBits::eRaygenKHR;
case EShLangIntersect:
return vk::ShaderStageFlagBits::eIntersectionKHR;
case EShLangAnyHit:
return vk::ShaderStageFlagBits::eAnyHitKHR;
case EShLangClosestHit:
return vk::ShaderStageFlagBits::eClosestHitKHR;
case EShLangMiss:
return vk::ShaderStageFlagBits::eMissKHR;
case EShLangCallable:
return vk::ShaderStageFlagBits::eCallableKHR;
case EShLangTask:
return vk::ShaderStageFlagBits::eTaskEXT;
case EShLangMesh:
return vk::ShaderStageFlagBits::eMeshEXT;
case EShLangCount:
break; // fall through
}
logAndDie("Invalid value passed to shaderStageFromGlslang!");
}
}
ShaderMeta reflectShader(glslang::TShader& shader)
{
return reflectIntermediate(*shader.getIntermediate(), shaderStageFromGlslang(shader.getStage()));
}
ShaderMeta reflectProgram(glslang::TProgram& program)
{
ShaderMeta result;
for (int stage = 0; stage < EShLangCount; ++stage)
{
glslang::TIntermediate* intermediate = program.getIntermediate(static_cast<EShLanguage>(stage));
if (intermediate == nullptr) {
continue;
}
result.extend(reflectIntermediate(*intermediate, shaderStageFromGlslang(static_cast<EShLanguage>(stage))));
}
return result;
}
ShaderMeta reflectIntermediate(glslang::TIntermediate& intermediate, vk::ShaderStageFlagBits stage)
{
ShaderMeta meta;
MetaCollectingTraverser traverser(meta, stage);
intermediate.getTreeRoot()->traverse(&traverser);
meta.stages.set(stage, true);
if (stage == vk::ShaderStageFlagBits::eCompute)
{
meta.localSizeX = static_cast<unsigned>(intermediate.getLocalSize(0));
meta.localSizeY = static_cast<unsigned>(intermediate.getLocalSize(1));
meta.localSizeZ = static_cast<unsigned>(intermediate.getLocalSize(2));
}
return meta;
}
} // namespace iwa

128
source/util/render_loop.cpp Normal file
View File

@@ -0,0 +1,128 @@
#include "iwa/util/render_loop.hpp"
#include <mijin/async/task_mutex.hpp>
#include "iwa/device.hpp"
#include "iwa/instance.hpp"
namespace iwa
{
namespace
{
// BIG BIG TODO: This is a dumb workaround for sharing images (e.g. the UI image) between multiple renderers.
// The reason is that the layout change mechanism doesn't work if multiple command buffers (that are executed
// sequentially) are recorded in parallel.
// A possible fix could be to move the state tracking mechanism to the renderer and generate the barriers
// before submitting.
mijin::TaskMutex gRenderMutex;
}
RenderLoop::RenderLoop(ObjectPtr<Device> owner, RenderLoopCreationArgs args)
: super_t(std::move(owner)), mAdvanceDeleteQueue(args.flags.advanceDeleteQueue)
{
mAlternating.resize(args.parallelFrames);
ObjectPtr<CommandPool> commandPool = std::move(args.commandPool);
if (!commandPool)
{
commandPool = getOwner()->createChild<CommandPool>(CommandPoolCreationArgs{
.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
.queueFamilyIndex = getOwner()->getDeviceInfo().graphicsQueueFamily
});
}
for (Alternating& alt : mAlternating)
{
alt.commandBuffer = commandPool->allocateCommandBuffer();
alt.renderDoneFence = getOwner()->createChild<Fence>(FenceCreationArgs{.flags = vk::FenceCreateFlagBits::eSignaled});
}
}
void RenderLoop::start() noexcept
{
addTask(c_renderLoop());
}
mijin::Task<> RenderLoop::c_init()
{
co_return;
}
mijin::SimpleTaskLoop& RenderLoop::getTaskLoop() const noexcept
{
return getOwner()->getOwner()->getMainTaskLoop();
}
mijin::Task<> RenderLoop::c_renderLoop()
{
co_await c_init();
while (!getOwner()->getOwner()->isQuitRequested())
{
Alternating& alt = mAlternating.at(mFrameIdx);
// wait for the command buffer to be ready
co_await alt.renderDoneFence->c_wait();
// reset the fence
alt.renderDoneFence->reset();
vk::CommandBuffer cmdBuffer = alt.commandBuffer->getVkHandle();
cmdBuffer.begin(vk::CommandBufferBeginInfo{
.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit
});
// record the commands
RenderLoopRenderArgs renderArgs = {
.cmdBuffer = *alt.commandBuffer,
.frameIdx = mFrameIdx
};
{ // gRenderMutex lock
const mijin::TaskMutexLock lock = co_await gRenderMutex.c_lock();
co_await c_render(renderArgs);
std::vector<vk::Semaphore> waitSemaphores;
std::vector<vk::Semaphore> signalSemaphores;
ImageReferenceFinalizeArgs finalizeArgs{
.cmdBuffer = *alt.commandBuffer,
.waitSemaphores = waitSemaphores,
.signalSemaphores = signalSemaphores
};
for (const ObjectPtr<ImageReference>& imageRef: renderArgs.usedImageReferences)
{
imageRef->finalize(finalizeArgs);
}
cmdBuffer.end();
// submit them
const vk::PipelineStageFlags waitStage = vk::PipelineStageFlagBits::eFragmentShader;
getOwner()->getGraphicsQueue().submit(vk::SubmitInfo{
.waitSemaphoreCount = static_cast<std::uint32_t>(waitSemaphores.size()),
.pWaitSemaphores = waitSemaphores.data(),
.pWaitDstStageMask = &waitStage,
.commandBufferCount = 1,
.pCommandBuffers = &cmdBuffer,
.signalSemaphoreCount = static_cast<std::uint32_t>(signalSemaphores.size()),
.pSignalSemaphores = signalSemaphores.data()
}, *alt.renderDoneFence);
} // gRenderMutex lock
// finally present
for (const ObjectPtr<ImageReference>& imageRef : renderArgs.usedImageReferences)
{
co_await imageRef->c_present();
}
// tick deleters
// TODO: what if there are multiple render loops?
if (mAdvanceDeleteQueue)
{
getOwner()->getOwner()->tickDeleteQueue();
}
mFrameIdx = (mFrameIdx + 1) % mAlternating.size();
}
co_return;
}
} // namespace iwa

676
source/util/shader_meta.cpp Normal file
View File

@@ -0,0 +1,676 @@
#include "iwa/util/shader_meta.hpp"
#include "iwa/log.hpp"
#include "kazan/resource/mesh.hpp"
#include "iwa/util/glsl_compiler.hpp"
#include "iwa/util/vkutil.hpp"
namespace
{
template<typename T>
inline std::size_t calcCrcSizeAppend(T, std::size_t) noexcept
{
MIJIN_TRAP(); // TODO
return 0;
}
}
namespace iwa
{
namespace
{
vk::ShaderStageFlags typeBitsToVkStages(ShaderTypeBits bits)
{
vk::ShaderStageFlags flags = {};
if (bits.compute)
{
flags |= vk::ShaderStageFlagBits::eCompute;
}
if (bits.vertex)
{
flags |= vk::ShaderStageFlagBits::eVertex;
}
if (bits.fragment)
{
flags |= vk::ShaderStageFlagBits::eFragment;
}
if (bits.rayGeneration)
{
flags |= vk::ShaderStageFlagBits::eRaygenKHR;
}
if (bits.rayClosestHit)
{
flags |= vk::ShaderStageFlagBits::eClosestHitKHR;
}
if (bits.rayAnyHit)
{
flags |= vk::ShaderStageFlagBits::eAnyHitKHR;
}
if (bits.rayMiss)
{
flags |= vk::ShaderStageFlagBits::eMissKHR;
}
if (bits.rayIntersection)
{
flags |= vk::ShaderStageFlagBits::eIntersectionKHR;
}
if (bits.callable)
{
flags |= vk::ShaderStageFlagBits::eCallableKHR;
}
return flags;
}
void addShaderAttribute(std::vector<ShaderAttribute>& attributes, ShaderAttribute&& attribute)
{
bool doInsert = true;
for (const ShaderAttribute& myAttribute: attributes)
{
if (myAttribute.stage == attribute.stage && myAttribute.location == attribute.location && myAttribute.location != UNSPECIFIED_INDEX)
{
// same location, type must be the same
if (myAttribute.type != attribute.type)
{
logAndDie(
"Attempting to merge incompatible shader metas, attributes {} and {} are incompatible. {} != {}",
myAttribute.name, attribute.name, myAttribute.type, attribute.type);
}
doInsert = false; // member already exists, don't insert
continue;
}
}
if (!doInsert)
{
return;
}
auto it = attributes.begin();
for (; it != attributes.end(); ++it)
{
if (static_cast<unsigned>(it->stage) > static_cast<unsigned>(attribute.stage)
|| (it->stage == attribute.stage && it->location > attribute.location))
{
break; // insert here
}
}
attributes.insert(it, std::move(attribute));
}
}
ShaderVariableStructType::ShaderVariableStructType() {} // NOLINT(modernize-use-equals-default)
ShaderVariableStructType::~ShaderVariableStructType() {} // NOLINT(modernize-use-equals-default)
void ShaderMeta::extendPushConstant(ShaderPushConstantBlock pushConstantBlock_, ShaderTypeBits stages)
{
if (pushConstantBlock_.type.baseType == ShaderVariableBaseType::NONE) {
return;
}
if (pushConstantBlock.type.baseType == ShaderVariableBaseType::NONE)
{
pushConstantBlock = std::move(pushConstantBlock_);
pushConstantStages = stages;
return;
}
// now comes the actual merging
assert(pushConstantBlock.type.baseType == ShaderVariableBaseType::STRUCT);
assert(pushConstantBlock_.type.baseType == ShaderVariableBaseType::STRUCT);
assert(stages);
for (ShaderVariableStructMember& member : pushConstantBlock_.type.struct_.members)
{
bool doInsert = true;
for (const ShaderVariableStructMember& myMember : pushConstantBlock.type.struct_.members)
{
if (myMember.offset == member.offset)
{
// same offset, type must be the same
if (myMember.type != member.type)
{
logAndDie("Attempting to merge incompatible push constant blocks, members {} and {} are incompatible. {} != {}",
myMember.name, member.name, myMember.type, member.type);
}
doInsert = false; // member already exists, don't insert
continue;
}
// otherwise check for overlaps
if ((myMember.offset < member.offset && myMember.offset + calcShaderTypeSize(myMember.type) > member.offset)
|| (myMember.offset > member.offset && myMember.offset < member.offset + calcShaderTypeSize(member.type)))
{
logAndDie("Attempting to merge incompatible push constant blocks, members {} and {} are overlapping.",
myMember.name, member.name);
}
}
if (!doInsert) {
continue;
}
auto it = pushConstantBlock.type.struct_.members.begin();
for (; it != pushConstantBlock.type.struct_.members.end(); ++it)
{
if (it->offset > member.offset) {
break; // insert here
}
}
pushConstantBlock.type.struct_.members.insert(it, std::move(member));
}
pushConstantStages |= stages;
}
void ShaderMeta::addInputAttribute(ShaderAttribute attribute)
{
addShaderAttribute(inputAttributes, std::move(attribute));
}
void ShaderMeta::addOutputAttribute(ShaderAttribute attribute)
{
addShaderAttribute(outputAttributes, std::move(attribute));
}
ObjectPtr<DescriptorSetLayout> DescriptorSetMeta::createDescriptorSetLayout(Device& device) const
{
assert(bindings.size() == bindingFlags.size());
return device.createChild<DescriptorSetLayout>(DescriptorSetLayoutCreationArgs{
.bindings = bindings,
.bindingFlags = bindingFlags,
.flags = flags,
});
}
std::vector<ObjectPtr<DescriptorSet>> PipelineAndDescriptorSetLayouts::createDescriptorSets(DescriptorPool& pool) const
{
std::vector<ObjectPtr<DescriptorSet>> result;
result.reserve(descriptorSetLayouts.size());
for (const ObjectPtr<DescriptorSetLayout>& layout : descriptorSetLayouts)
{
result.push_back(pool.allocateDescriptorSet({
.layout = layout
}));
}
return result;
}
ObjectPtr<DescriptorSet> PipelineAndDescriptorSetLayouts::createDescriptorSet(DescriptorPool& pool, unsigned setIdx) const
{
MIJIN_ASSERT(setIdx < descriptorSetLayouts.size(), "Invalid set index.");
return pool.allocateDescriptorSet({
.layout = descriptorSetLayouts[setIdx]
});
}
PipelineAndDescriptorSetLayouts PipelineLayoutMeta::createPipelineLayout(Device& device) const
{
std::vector<ObjectPtr<DescriptorSetLayout>> descSetLayouts;
descSetLayouts.reserve(descriptorSets.size());
for (const DescriptorSetMeta& dslMeta : descriptorSets)
{
descSetLayouts.push_back(dslMeta.createDescriptorSetLayout(device));
}
std::vector<vk::PushConstantRange> pushConstantRanges;
if (pushConstantRange.stageFlags)
{
pushConstantRanges.push_back(pushConstantRange);
}
ObjectPtr<PipelineLayout> pipelineLayout = device.createChild<PipelineLayout>(PipelineLayoutCreationArgs{
.setLayouts = descSetLayouts,
.pushConstantRanges = std::move(pushConstantRanges)
});
return
{
.descriptorSetLayouts = std::move(descSetLayouts),
.pipelineLayout = std::move(pipelineLayout)
};
}
void ShaderVariable::verifyCompatible(const ShaderVariable& other) const
{
std::vector<std::string> errors;
if (other.binding != binding) {
errors.push_back(fmt::format("Variable bindings do not match: {} != {}.", binding, other.binding)); // NOLINT
}
if (other.descriptorType != descriptorType) {
errors.push_back(fmt::format("Descriptor types do not match: {} != {}.",
magic_enum::enum_name(descriptorType),
magic_enum::enum_name(other.descriptorType)));
}
if (other.name != name) {
logMsg("Warning: shader variable names do not match, variable will only be referrable to by one of them! ({} != {})",
name, other.name);
}
if (other.type != type) {
errors.push_back(fmt::format("Variable types do not match: {} != {}.", type, other.type));
}
if (errors.empty()) {
return;
}
logMsg("Error(s) verifying shader variable compatibility:");
for (const std::string& error : errors) {
logMsg(error);
}
std::abort();
}
std::size_t ShaderVariable::calcHash(std::size_t appendTo) const
{
(void) appendTo;
MIJIN_TRAP(); // TODO
return 0;
#if 0
std::size_t hash = appendTo;
hash = type.calcHash(hash);
hash = calcCrcSizeAppend(descriptorType, hash);
hash = calcCrcSizeAppend(binding, hash);
hash = calcCrcSizeAppend(name, hash);
return hash;
#endif
}
#if 0
ShaderSource ShaderSource::fromFile(std::string fileName, std::string name)
{
(void) fileName;
(void) name;
MIJIN_TRAP(); // TODO
return {};
std::string code = readFileText(fileName);
return {
.code = std::move(code),
.fileName = std::move(fileName),
#if !defined(KAZAN_RELEASE)
.name = std::move(name)
#endif
};
}
#endif
bool ShaderVariableSet::find(std::string_view varName, ShaderVariableFindResult& outResult) const noexcept
{
for (const ShaderVariable& var : variables)
{
if (var.name == varName)
{
outResult.setIndex = setIndex;
outResult.bindIndex = var.binding;
return true;
}
}
return false;
}
bool ShaderVariableSet::find(unsigned semantic, unsigned semanticIdx, ShaderVariableFindResult& outResult) const noexcept
{
for (const ShaderVariable& var : variables)
{
if (var.semantic == semantic && var.semanticIndex == semanticIdx)
{
outResult.setIndex = setIndex;
outResult.bindIndex = var.binding;
return true;
}
}
return false;
}
const ShaderVariable& ShaderVariableSet::getVariableAtBinding(unsigned bindingIdx) const
{
for (const ShaderVariable& var : variables)
{
if (var.binding == bindingIdx)
{
return var;
}
}
logAndDie("Could not find shader variable with binding {}!", bindingIdx);
}
const ShaderVariable* ShaderVariableSet::getVariableAtBindingOpt(unsigned bindingIdx) const
{
for (const ShaderVariable& var : variables)
{
if (var.binding == bindingIdx)
{
return &var;
}
}
return nullptr;
}
const ShaderVariable* ShaderVariableSet::getVariableAtSemanticOpt(unsigned semantic, unsigned semanticIdx) const
{
for (const ShaderVariable& var : variables)
{
if (var.semantic == semantic && var.semanticIndex == semanticIdx)
{
return &var;
}
}
return nullptr;
}
std::size_t ShaderVariableSet::calcHash(std::size_t appendTo) const
{
std::size_t hash = appendTo;
for (const ShaderVariable& var : variables) {
hash = var.calcHash(hash);
}
return hash;
}
void ShaderMeta::extend(ShaderMeta other)
{
for (ShaderVariableSet& set : other.interfaceVariableSets)
{
ShaderVariableSet& mySet = getOrCreateInterfaceVariableSet(set.setIndex);
mySet.usedInStages.bits |= set.usedInStages.bits;
for (ShaderVariable& variable : set.variables)
{
const ShaderVariable* myVariable = nullptr;
if (variable.binding != UNSPECIFIED_INDEX)
{
myVariable = mySet.getVariableAtBindingOpt(variable.binding);
}
else if (variable.semantic != UNSPECIFIED_INDEX)
{
myVariable = mySet.getVariableAtSemanticOpt(variable.semantic, variable.semanticIndex);
}
if (myVariable)
{
myVariable->verifyCompatible(variable);
continue;
}
mySet.variables.push_back(std::move(variable));
}
}
for (ShaderAttribute& attribute : other.inputAttributes)
{
addInputAttribute(std::move(attribute));
}
for (ShaderAttribute& attribute : other.outputAttributes)
{
addOutputAttribute(std::move(attribute));
}
extendPushConstant(other.pushConstantBlock, other.pushConstantStages);
stages |= other.stages;
if (localSizeX == 0 && localSizeY == 0 && localSizeZ == 0)
{
localSizeX = other.localSizeX;
localSizeY = other.localSizeY;
localSizeZ = other.localSizeZ;
}
else if ((other.localSizeX != 0 || other.localSizeY != 0 || other.localSizeZ != 0) &&
(localSizeX != other.localSizeX || localSizeY != other.localSizeY || localSizeZ != other.localSizeZ))
{
logAndDie("Error merging shader metas, conflicting local size!");
}
hash = 0;
}
bool ShaderMeta::findInterfaceVariable(std::string_view varName, ShaderVariableFindResult& outResult) const noexcept
{
for (const ShaderVariableSet& set : interfaceVariableSets)
{
if (set.find(varName, outResult)) {
return true;
}
}
return false;
}
bool ShaderMeta::findInterfaceVariable(unsigned semantic, unsigned semanticIdx, ShaderVariableFindResult& outResult) const noexcept
{
for (const ShaderVariableSet& set : interfaceVariableSets)
{
if (set.find(semantic, semanticIdx, outResult)) {
return true;
}
}
return false;
}
const ShaderVariableSet& ShaderMeta::getInterfaceVariableSet(unsigned setIdx) const
{
const ShaderVariableSet* variableSet = getInterfaceVariableSetOpt(setIdx);
MIJIN_ASSERT(variableSet != nullptr, "Could not find interface variable set.");
return *variableSet;
}
const ShaderVariableSet* ShaderMeta::getInterfaceVariableSetOpt(unsigned setIdx) const
{
for (const ShaderVariableSet& set : interfaceVariableSets)
{
if (set.setIndex == setIdx) {
return &set;
}
}
return nullptr;
}
const ShaderVariableType& ShaderMeta::getInterfaceVariableType(unsigned setIdx, unsigned bindingIdx) const
{
return getInterfaceVariableSet(setIdx).getVariableAtBinding(bindingIdx).type;
}
VertexInput ShaderMeta::generateVertexInput(const NamedVertexInput& namedInput) const noexcept
{
VertexInput result{
.bindings = namedInput.bindings
};
for (const ShaderAttribute& attribute : inputAttributes)
{
if (attribute.stage != vk::ShaderStageFlagBits::eVertex) {
continue;
}
MIJIN_ASSERT_FATAL(attribute.type.baseType == ShaderVariableBaseType::SIMPLE, "Vertex shader input must be a simple type.");
auto itAttribute = namedInput.attributes.find(attribute.name);
MIJIN_ASSERT_FATAL(itAttribute != namedInput.attributes.end(), "Missing attribute in input.");
result.attributes.push_back(vk::VertexInputAttributeDescription{
.location = attribute.location,
.binding = itAttribute->second.binding,
.format = attribute.type.simple.format,
.offset = itAttribute->second.offset
});
}
return result;
}
VertexInput ShaderMeta::generateVertexInputFromLayout(const VertexLayout& layout) const noexcept
{
VertexInput result{
.bindings = {
vk::VertexInputBindingDescription{
.binding = 0,
.stride = layout.stride,
.inputRate = vk::VertexInputRate::eVertex
}
}
};
for (const ShaderAttribute& attribute : inputAttributes)
{
if (attribute.stage != vk::ShaderStageFlagBits::eVertex) {
continue;
}
if (attribute.semantic == UNSPECIFIED_INDEX) {
continue;
}
MIJIN_ASSERT_FATAL(attribute.type.baseType == ShaderVariableBaseType::SIMPLE, "Vertex shader input must be a simple type.");
auto itAttribute = std::ranges::find_if(layout.attributes, [&attribute](const VertexAttribute& attrib) {
return static_cast<unsigned>(attrib.semantic) == attribute.semantic && attrib.semanticIdx == attribute.semanticIndex;
});
MIJIN_ASSERT_FATAL(itAttribute != layout.attributes.end(), "Missing attribute in vertex layout.");
result.attributes.push_back(vk::VertexInputAttributeDescription{
.location = attribute.location,
.binding = 0,
.format = attribute.type.simple.format,
.offset = itAttribute->offset
});
}
return result;
}
DescriptorSetMeta ShaderMeta::generateDescriptorSetLayout(const ShaderVariableSet& set, const GenerateDescriptorSetLayoutArgs& args) const
{
DescriptorSetMeta setInfo{
.flags = args.flags
};
for (const ShaderVariable& var : set.variables)
{
auto itVar = std::ranges::find_if(setInfo.bindings, [&](const vk::DescriptorSetLayoutBinding& binding) {
return binding.binding == var.binding;
});
assert(itVar == setInfo.bindings.end()); // should have been merged!
if (itVar != setInfo.bindings.end())
{
itVar->stageFlags |= typeBitsToVkStages(set.usedInStages);
continue; // TODO: verify the bindings are compatible
}
vk::DescriptorSetLayoutBinding& binding = setInfo.bindings.emplace_back();
vk::DescriptorBindingFlags& flags = setInfo.bindingFlags.emplace_back();
binding.binding = var.binding;
binding.descriptorType = var.descriptorType;
binding.descriptorCount = 1;
binding.stageFlags = typeBitsToVkStages(set.usedInStages);
// support for dynamically sized descriptors
auto itCounts = args.descriptorCounts.find(var.binding);
if (itCounts != args.descriptorCounts.end() && itCounts->second > 0)
{
binding.descriptorCount = itCounts->second;
flags |= vk::DescriptorBindingFlagBits::ePartiallyBound;
}
if (setInfo.descriptorTypes.size() <= var.binding) {
setInfo.descriptorTypes.resize(var.binding + 1);
}
setInfo.descriptorTypes[var.binding] = var.descriptorType;
}
return setInfo;
}
PipelineLayoutMeta ShaderMeta::generatePipelineLayout(const GeneratePipelineLayoutArgs& args) const
{
static const std::vector<std::uint32_t> NO_DESCRIPTOR_COUNTS = {};
static const GenerateDescriptorSetLayoutArgs NO_DESCRIPTOR_SET_ARGS = {};
PipelineLayoutMeta result;
for (const ShaderVariableSet& set : interfaceVariableSets)
{
if (set.setIndex >= result.descriptorSets.size()) {
result.descriptorSets.resize(set.setIndex + 1);
}
auto itSet = args.descriptorSets.find(set.setIndex);
const GenerateDescriptorSetLayoutArgs setArgs =
itSet != args.descriptorSets.end()
? itSet->second
: NO_DESCRIPTOR_SET_ARGS;
result.descriptorSets[set.setIndex] = generateDescriptorSetLayout(set, setArgs);
}
if (pushConstantBlock.type.baseType != ShaderVariableBaseType::NONE)
{
assert(pushConstantStages);
result.pushConstantRange.stageFlags = typeBitsToVkStages(pushConstantStages);
result.pushConstantRange.size = pushConstantBlock.offset + calcShaderTypeSize(pushConstantBlock.type);
}
return result;
}
bool ShaderMeta::empty() const
{
static_assert(ShaderMeta::STRUCT_VERSION == 1, "Update me");
return interfaceVariableSets.empty()
&& inputAttributes.empty()
&& outputAttributes.empty()
&& pushConstantStages == ShaderTypeBits()
&& pushConstantBlock.type.baseType == ShaderVariableBaseType::NONE
&& localSizeX == 0
&& localSizeY == 0
&& localSizeZ == 0;
}
std::size_t ShaderMeta::getHash() const
{
if (hash == 0)
{
hash = 1; // TODO
MIJIN_TRAP();
#if 0
for (const ShaderVariableSet& variableSet : interfaceVariableSets) {
hash = variableSet.calcHash(hash);
}
hash = calcCrcSizeAppend(pushConstantStages.bits, hash);
hash = pushConstantBlock.type.calcHash(hash);
hash = calcCrcSizeAppend(pushConstantBlock.offset, hash);
hash = calcCrcSizeAppend(localSizeX, hash);
hash = calcCrcSizeAppend(localSizeY, hash);
hash = calcCrcSizeAppend(localSizeZ, hash);
#endif
}
return hash;
}
unsigned calcShaderTypeSize(const ShaderVariableType& type, bool ignoreArraySize) noexcept
{
unsigned size = 0;
switch (type.baseType)
{
case ShaderVariableBaseType::SIMPLE:
size = vkFormatSize(type.simple.format);
break;
case ShaderVariableBaseType::MATRIX:
switch (type.matrixType)
{
case ShaderVariableMatrixType::MAT2:
size = 16;
break;
case ShaderVariableMatrixType::MAT3:
size = 36;
break;
case ShaderVariableMatrixType::MAT4:
size = 64;
break;
default:
logAndDie("Lol, what's this?");
}
break;
case ShaderVariableBaseType::STRUCT:
assert(!type.struct_.members.empty());
size = static_cast<unsigned>(type.struct_.members.back().offset + calcShaderTypeSize(type.struct_.members.back().type));
break;
default:
logAndDie("How would I know?");
}
if (!ignoreArraySize) {
size *= type.arraySize;
}
return size;
}
} // namespace iwa

View File

@@ -0,0 +1,402 @@
#include "iwa/util/texture_atlas.hpp"
#include <bit>
#include "iwa/device.hpp"
#include "iwa/instance.hpp"
#include "iwa/resource/bitmap.hpp"
namespace iwa
{
TextureSlot::TextureSlot(ObjectPtr<TextureAtlas> owner, const TextureSlotCreationArgs& args)
: super_t(std::move(owner)), mUsedSpace(args.usedSpace), mLayer(args.layer), mUvOffset(args.uvOffset), mUvScale(args.uvScale)
{
}
TextureAtlas::TextureAtlas(ObjectPtr<> owner, const TextureAtlasCreationArgs& args)
: super_t(std::move(owner)), mLayerSize(args.layerSize)
{
// start with a single layer with one free space that takes up the entire layer
mLayers.push_back({
.freeSpaces = {
vk::Rect2D{
.offset = { .x = 0, .y = 0 },
.extent = args.layerSize
}
}
});
}
ObjectPtr<TextureSlot> TextureAtlas::allocateSlot(vk::Extent2D slotSize)
{
// only uses multiples of 2
// TODO: check if this actually improves the results
const vk::Extent2D size = {
.width = std::bit_ceil(slotSize.width),
.height = std::bit_ceil(slotSize.height)
};
// check if it can even fit
if (size.width > mLayerSize.width || size.height > mLayerSize.height) {
throw std::runtime_error("Cannot allocate texture slot, size too big.");
}
// find the best fit (minimize product of "wasted" space)
unsigned lowestWasteSum = std::numeric_limits<unsigned>::max();
unsigned lowestWasteProduct = std::numeric_limits<unsigned>::max();
std::vector<TextureAtlasLayer>::iterator foundLayer = mLayers.end();
std::vector<vk::Rect2D>::iterator foundSpace;
for (auto itLayer = mLayers.begin(); itLayer != mLayers.end(); ++itLayer)
{
for (auto itSpace = itLayer->freeSpaces.begin(); itSpace != itLayer->freeSpaces.end(); ++itSpace)
{
if (itSpace->extent.width < size.width || itSpace->extent.height < size.height) {
continue;
}
const unsigned wasteWidth = itSpace->extent.width - size.width;
const unsigned wasteHeight = itSpace->extent.height - size.height;
const unsigned wasteProduct = wasteWidth * wasteHeight;
if (wasteProduct <= lowestWasteProduct)
{
const unsigned wasteSum = wasteWidth + wasteHeight;
if (wasteProduct < lowestWasteProduct || wasteSum < lowestWasteSum)
{
lowestWasteSum = wasteSum;
lowestWasteProduct = wasteProduct;
foundLayer = itLayer;
foundSpace = itSpace;
}
}
} // for (itLayer->freeSpaces)
} // for (mLayers)
// if no space was found, make space
if (foundLayer == mLayers.end())
{
mLayers.resize(mLayers.size() + 1);
mLayers.back().freeSpaces.push_back({
.offset = { .x = 0, .y = 0},
.extent = mLayerSize
});
foundLayer = std::prev(mLayers.end());
foundSpace = foundLayer->freeSpaces.begin();
}
// save in case the iterator gets invalidated
const vk::Rect2D space = *foundSpace;
// remove it
foundLayer->freeSpaces.erase(foundSpace);
// now split the space, if necessary
const bool splitX = space.extent.width > size.width;
const bool splitY = space.extent.height > size.height;
if (splitX)
{
foundLayer->freeSpaces.push_back({
.offset = {
.x = static_cast<std::int32_t>(space.offset.x + size.width),
.y = space.offset.y
},
.extent = {
.width = space.extent.width - size.width,
.height = size.height
}
});
}
if (splitY)
{
foundLayer->freeSpaces.push_back({
.offset = {
.x = space.offset.x,
.y = static_cast<std::int32_t>(space.offset.y + size.height)
},
.extent = {
.width = size.width,
.height = space.extent.height - size.height
}
});
}
if (splitX && splitY)
{
foundLayer->freeSpaces.push_back({
.offset = {
.x = static_cast<std::int32_t>(space.offset.x + size.width),
.y = static_cast<std::int32_t>(space.offset.y + size.height)
},
.extent = {
.width = space.extent.width - size.width,
.height = space.extent.height - size.height
}
});
}
// return the result
return createChild<TextureSlot>(TextureSlotCreationArgs{
.usedSpace = {
.offset = space.offset,
.extent = slotSize
},
.layer = static_cast<unsigned>(std::distance(mLayers.begin(), foundLayer)),
.uvOffset = {
static_cast<float>(space.offset.x) / static_cast<float>(mLayerSize.width),
static_cast<float>(space.offset.y) / static_cast<float>(mLayerSize.height)
},
.uvScale = {
static_cast<float>(slotSize.width) / static_cast<float>(mLayerSize.width),
static_cast<float>(slotSize.height) / static_cast<float>(mLayerSize.height)
}
});
}
AtlasedImage::AtlasedImage(ObjectPtr<Device> owner, const AtlasedImageCreationArgs& args)
: super_t(std::move(owner)), mAtlas(TextureAtlas::create(TextureAtlasCreationArgs{.layerSize = args.size})),
mFormat(args.format), mMipLevels(args.mipLevels), mUsage(args.usage | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst)
{
mImage = allocateImage(args.initialLayers);
mImageView = mImage->createImageView({
.viewType = vk::ImageViewType::e2DArray
});
}
mijin::Task<ObjectPtr<TextureSlot>> AtlasedImage::c_allocateSlot(vk::Extent2D slotSize)
{
IWA_CORO_ENSURE_MAIN_THREAD(*getOwner()->getOwner());
ObjectPtr<TextureSlot> slot = mAtlas->allocateSlot(slotSize);
if (slot->getLayer() >= mImage->getArrayLayers())
{
const mijin::TaskMutexLock lock = co_await mImageMutex.c_lock();
// image is too small, resize it
// this includes a complete copy of the existing image
ObjectPtr<Image> newImage = allocateImage(slot->getLayer() + 1);
ObjectPtr<CommandBuffer> cmdBufferPtr = getOwner()->beginScratchCommandBuffer();
vk::CommandBuffer cmdBuffer = *cmdBufferPtr;
mImage->applyTransition(cmdBuffer, IMAGE_TRANSITION_TRANSFER_READ);
newImage->applyTransition(cmdBuffer, IMAGE_TRANSITION_TRANSFER_WRITE);
// copy ALL the mip levels
std::vector<vk::ImageCopy> regions;
regions.reserve(mImage->getMipLevels());
for (unsigned level = 0; level < mImage->getMipLevels(); ++level)
{
const vk::ImageSubresourceLayers copySubresource{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = level,
.baseArrayLayer = 0,
.layerCount = mImage->getArrayLayers()
};
regions.push_back({
.srcSubresource = copySubresource,
.srcOffset = {.x = 0, .y = 0, .z = 0},
.dstSubresource = copySubresource,
.dstOffset = {.x = 0, .y = 0, .z = 0},
.extent = {
.width = mAtlas->getLayerSize().width,
.height = mAtlas->getLayerSize().height,
.depth = 1
}
});
}
cmdBuffer.copyImage(
/* srcImage = */ *mImage,
/* srcImageLayout = */ vk::ImageLayout::eTransferSrcOptimal,
/* dstImage = */ *newImage,
/* dstImageLayout = */ vk::ImageLayout::eTransferDstOptimal,
/* regions = */ regions
);
co_await getOwner()->endScratchCommandBuffer(cmdBufferPtr);
mImage = std::move(newImage);
mImageView = mImage->createImageView({
.viewType = vk::ImageViewType::e2DArray
});
imageRecreated.emit();
}
co_return slot;
}
mijin::Task<> AtlasedImage::c_upload(const TextureSlot& slot, const Bitmap& bitmap) const noexcept
{
IWA_CORO_ENSURE_MAIN_THREAD(*getOwner()->getOwner());
MIJIN_ASSERT(slot.getUsedSpace().extent.width >= bitmap.getSize().width
&& slot.getUsedSpace().extent.height >= bitmap.getSize().height, "Can't upload image, invalid size.");
const mijin::TaskMutexLock lock = co_await mImageMutex.c_lock();
co_await mImage->c_upload(
/* bitmap = */ bitmap,
/* imageOffset = */ {
.x = slot.getUsedSpace().offset.x,
.y = slot.getUsedSpace().offset.y,
.z = 0
},
/* baseLayer = */ slot.getLayer()
);
}
mijin::Task<> AtlasedImage::c_upload(const TextureSlot& slot, const void* data, std::size_t bytes, const vk::Extent2D& bufferImageSize) const noexcept
{
IWA_CORO_ENSURE_MAIN_THREAD(*getOwner()->getOwner());
MIJIN_ASSERT(slot.getUsedSpace().extent.width >= bufferImageSize.width
&& slot.getUsedSpace().extent.height >= bufferImageSize.height, "Can't upload image, invalid size.");
const mijin::TaskMutexLock lock = co_await mImageMutex.c_lock();
co_await mImage->c_upload(
/* data = */ data,
/* bytes = */ bytes,
/* bufferImageSize = */ {
.width = bufferImageSize.width,
.height = bufferImageSize.height,
.depth = 1
},
/* imageOffset = */ {
.x = slot.getUsedSpace().offset.x,
.y = slot.getUsedSpace().offset.y,
.z = 0
},
/* baseLayer = */ slot.getLayer()
);
}
mijin::Task<> AtlasedImage::c_blit(const TextureSlot& slot, Image& srcImage) const noexcept
{
IWA_CORO_ENSURE_MAIN_THREAD(*getOwner()->getOwner());
MIJIN_ASSERT(slot.getUsedSpace().extent.width >= srcImage.getSize().width
&& slot.getUsedSpace().extent.height >= srcImage.getSize().height
&& srcImage.getSize().depth == 1, "Can't upload image, invalid size.");
const mijin::TaskMutexLock lock = co_await mImageMutex.c_lock();
co_await mImage->c_blitFrom(
/* srcImage = */ srcImage,
/* regions = */ {
vk::ImageBlit{
.srcSubresource = DEFAULT_SUBRESOURCE_LAYERS,
.srcOffsets = std::array{
vk::Offset3D{
.x = 0, .y = 0, .z = 0
},
vk::Offset3D{
.x = static_cast<std::int32_t>(srcImage.getSize().width),
.y = static_cast<std::int32_t>(srcImage.getSize().height),
.z = 1
}
},
.dstSubresource = vk::ImageSubresourceLayers{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = 0,
.baseArrayLayer = slot.getLayer(),
.layerCount = 1
},
.dstOffsets = std::array{
vk::Offset3D{
.x = slot.getUsedSpace().offset.x,
.y = slot.getUsedSpace().offset.y,
.z = 0
},
vk::Offset3D{
.x = slot.getUsedSpace().offset.x + static_cast<std::int32_t>(slot.getUsedSpace().extent.width),
.y = slot.getUsedSpace().offset.y + static_cast<std::int32_t>(slot.getUsedSpace().extent.height),
.z = 1
}
}
}
}
);
}
mijin::Task<> AtlasedImage::c_blit(const TextureSlot& slot, const Bitmap& bitmap) const noexcept
{
IWA_CORO_ENSURE_MAIN_THREAD(*getOwner()->getOwner());
MIJIN_ASSERT(slot.getUsedSpace().extent.width >= bitmap.getSize().width
&& slot.getUsedSpace().extent.height >= bitmap.getSize().height, "Can't upload image, invalid size.");
const mijin::TaskMutexLock lock = co_await mImageMutex.c_lock();
co_await mImage->c_blitFrom(
/* bitmap = */ bitmap,
/* regions = */ {
vk::ImageBlit{
.srcSubresource = DEFAULT_SUBRESOURCE_LAYERS,
.srcOffsets = std::array{
vk::Offset3D{
.x = 0, .y = 0, .z = 0
},
vk::Offset3D{
.x = static_cast<std::int32_t>(bitmap.getSize().width),
.y = static_cast<std::int32_t>(bitmap.getSize().height),
.z = 1
}
},
.dstSubresource = vk::ImageSubresourceLayers{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = 0,
.baseArrayLayer = slot.getLayer(),
.layerCount = 1
},
.dstOffsets = std::array{
vk::Offset3D{
.x = slot.getUsedSpace().offset.x,
.y = slot.getUsedSpace().offset.y,
.z = 0
},
vk::Offset3D{
.x = slot.getUsedSpace().offset.x + static_cast<std::int32_t>(slot.getUsedSpace().extent.width),
.y = slot.getUsedSpace().offset.y + static_cast<std::int32_t>(slot.getUsedSpace().extent.height),
.z = 1
}
}
}
}
);
}
mijin::Task<> AtlasedImage::c_copy(const TextureSlot& slot, Image& srcImage) const noexcept
{
IWA_CORO_ENSURE_MAIN_THREAD(*getOwner()->getOwner());
MIJIN_ASSERT(slot.getUsedSpace().extent.width >= srcImage.getSize().width
&& slot.getUsedSpace().extent.height >= srcImage.getSize().height
&& srcImage.getSize().depth == 1, "Can't upload image, invalid size.");
const mijin::TaskMutexLock lock = co_await mImageMutex.c_lock();
co_await mImage->c_copyFrom(
/* srcImage = */ srcImage,
/* regions = */ {
vk::ImageCopy{
.srcSubresource = DEFAULT_SUBRESOURCE_LAYERS,
.srcOffset = {
.x = 0, .y = 0, .z = 0
},
.dstSubresource = vk::ImageSubresourceLayers{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = 0,
.baseArrayLayer = slot.getLayer(),
.layerCount = 1
},
.dstOffset = {
.x = slot.getUsedSpace().offset.x,
.y = slot.getUsedSpace().offset.y,
.z = 0
},
.extent = srcImage.getSize()
}
}
);
}
ObjectPtr<Image> AtlasedImage::allocateImage(unsigned layers)
{
ObjectPtr<Image> image = getOwner()->createChild<Image>(ImageCreationArgs{
.format = mFormat,
.extent = {
.width = mAtlas->getLayerSize().width,
.height = mAtlas->getLayerSize().height,
.depth = 1
},
.mipLevels = mMipLevels,
.arrayLayers = layers,
.usage = mUsage
});
image->allocateMemory();
return image;
}
} // namespace iwa

View File

@@ -0,0 +1,29 @@
#include "iwa/util/vertex_layout.hpp"
namespace iwa
{
mijin::Optional<VertexAttribute&> VertexLayout::findAttribute(VertexAttributeSemantic semantic, unsigned semanticIdx) noexcept
{
for (VertexAttribute& attribute : attributes)
{
if (attribute.semantic == semantic && attribute.semanticIdx == semanticIdx)
{
return attribute;
}
}
return mijin::NULL_OPTIONAL;
}
mijin::Optional<const VertexAttribute&> VertexLayout::findAttribute(VertexAttributeSemantic semantic, unsigned semanticIdx) const noexcept
{
for (const VertexAttribute& attribute : attributes)
{
if (attribute.semantic == semantic && attribute.semanticIdx == semanticIdx)
{
return attribute;
}
}
return mijin::NULL_OPTIONAL;
}
} // namespace iwa

275
source/util/vkutil.cpp Normal file
View File

@@ -0,0 +1,275 @@
#include "iwa/util/vkutil.hpp"
#include "iwa/device.hpp"
#include "iwa/log.hpp"
namespace iwa
{
unsigned vkFormatSize(vk::Format format) noexcept
{
switch (format)
{
// 8 bit integer
case vk::Format::eR8Uint:
case vk::Format::eR8Sint:
case vk::Format::eR8Unorm:
case vk::Format::eR8Srgb:
return 1;
case vk::Format::eR8G8Uint:
case vk::Format::eR8G8Sint:
case vk::Format::eR8G8Unorm:
case vk::Format::eR8G8Srgb:
return 2;
case vk::Format::eR8G8B8Uint:
case vk::Format::eR8G8B8Sint:
case vk::Format::eR8G8B8Unorm:
case vk::Format::eR8G8B8Srgb:
return 3;
case vk::Format::eR8G8B8A8Uint:
case vk::Format::eR8G8B8A8Sint:
case vk::Format::eR8G8B8A8Unorm:
case vk::Format::eR8G8B8A8Srgb:
return 4;
// 16 bit integer
case vk::Format::eR16Uint:
case vk::Format::eR16Sint:
case vk::Format::eR16Unorm:
return 2;
case vk::Format::eR16G16Uint:
case vk::Format::eR16G16Sint:
case vk::Format::eR16G16Unorm:
return 4;
case vk::Format::eR16G16B16Uint:
case vk::Format::eR16G16B16Sint:
case vk::Format::eR16G16B16Unorm:
return 6;
case vk::Format::eR16G16B16A16Uint:
case vk::Format::eR16G16B16A16Sint:
case vk::Format::eR16G16B16A16Unorm:
return 8;
// 32 bit integer
case vk::Format::eR32Uint:
case vk::Format::eR32Sint:
return 4;
case vk::Format::eR32G32Uint:
case vk::Format::eR32G32Sint:
return 8;
case vk::Format::eR32G32B32Uint:
case vk::Format::eR32G32B32Sint:
return 12;
case vk::Format::eR32G32B32A32Uint:
case vk::Format::eR32G32B32A32Sint:
return 16;
// 64 bit integer
case vk::Format::eR64Uint:
case vk::Format::eR64Sint:
return 8;
case vk::Format::eR64G64Uint:
case vk::Format::eR64G64Sint:
return 16;
case vk::Format::eR64G64B64Uint:
case vk::Format::eR64G64B64Sint:
return 24;
case vk::Format::eR64G64B64A64Uint:
case vk::Format::eR64G64B64A64Sint:
return 32;
// 16 bit float
case vk::Format::eR16Sfloat:
return 2;
case vk::Format::eR16G16Sfloat:
return 4;
case vk::Format::eR16G16B16Sfloat:
return 6;
case vk::Format::eR16G16B16A16Sfloat:
return 8;
// 32 bit float
case vk::Format::eR32Sfloat:
return 4;
case vk::Format::eR32G32Sfloat:
return 8;
case vk::Format::eR32G32B32Sfloat:
return 12;
case vk::Format::eR32G32B32A32Sfloat:
return 16;
// 64 bit float
case vk::Format::eR64Sfloat:
return 8;
case vk::Format::eR64G64Sfloat:
return 16;
case vk::Format::eR64G64B64Sfloat:
return 24;
case vk::Format::eR64G64B64A64Sfloat:
return 32;
default:
logAndDie("I've never seen this format :(");
}
}
unsigned vkIndexTypeSize(vk::IndexType indexType) noexcept
{
switch (indexType)
{
case vk::IndexType::eNoneKHR:
return 0;
case vk::IndexType::eUint8EXT:
return 1;
case vk::IndexType::eUint16:
return 2;
case vk::IndexType::eUint32:
return 4;
default:
logAndDie("What is this sorcery?");
}
}
bool isDepthFormat(vk::Format format) noexcept
{
for (const vk::Format depthFormat : DEPTH_FORMATS) {
if (format == depthFormat) {
return true;
}
}
return false;
}
bool isStencilFormat(vk::Format format) noexcept
{
for (const vk::Format stencilFormat : STENCIL_FORMATS) {
if (format == stencilFormat) {
return true;
}
}
return false;
}
#if 0
std::string formatVkVariant(const VkVariantMWN& variant)
{
switch (variant.type)
{
case VK_VARIANT_TYPE_UNKNOWN_MWN:
return "???";
case VK_VARIANT_TYPE_NONE_MWN:
return "<none>";
case VK_VARIANT_TYPE_BOOL_MWN:
return variant.uintValue ? "true" : "false";
case VK_VARIANT_TYPE_UINT8_MWN:
case VK_VARIANT_TYPE_UINT16_MWN:
case VK_VARIANT_TYPE_UINT32_MWN:
case VK_VARIANT_TYPE_UINT64_MWN:
return std::to_string(variant.uintValue);
case VK_VARIANT_TYPE_INT8_MWN:
case VK_VARIANT_TYPE_INT16_MWN:
case VK_VARIANT_TYPE_INT32_MWN:
case VK_VARIANT_TYPE_INT64_MWN:
return std::to_string(variant.intValue);
case VK_VARIANT_TYPE_FLOAT_MWN:
case VK_VARIANT_TYPE_DOUBLE_MWN:
return std::to_string(variant.doubleValue);
case VK_VARIANT_TYPE_STRING_MWN:
return fmt::format("\"{}\"", variant.stringValue);
case VK_VARIANT_TYPE_VOID_POINTER_MWN:
return fmt::format("{}", fmt::ptr(variant.voidPointerValue)); // TODO: this doesnt make sense, store the original pointer!
case VK_VARIANT_TYPE_POINTER_MWN:
return fmt::format("{}", fmt::ptr(variant.pointerValue)); // TODO: this doesnt make sense, store the original pointer!
case VK_VARIANT_TYPE_ARRAY_MWN:
return fmt::format("<array of {}>", variant.arrayValue.numElements);
case VK_VARIANT_TYPE_IN_STRUCTURE_MWN:
return "<in struct>";
case VK_VARIANT_TYPE_OUT_STRUCTURE_MWN:
return "<out struct>";
case VK_VARIANT_TYPE_OBJECT_MWN:
return "<handle>";
default:
assert(0);
return "???";
}
}
#endif
#if 0
std::size_t calcVkStructHash(const void* structure, std::size_t appendTo)
{
if (structure == nullptr) {
return appendTo;
}
const vk::BaseInStructure* inStruct = static_cast<const vk::BaseInStructure*>(structure);
std::size_t hash = appendTo;
switch (inStruct->sType)
{
case vk::StructureType::eDescriptorSetLayoutBindingFlagsCreateInfo: {
const auto& flagsInfo = *static_cast<const vk::DescriptorSetLayoutBindingFlagsCreateInfo*>(structure);
for (std::uint32_t bindingIdx = 0; bindingIdx < flagsInfo.bindingCount; ++bindingIdx) {
hash = calcCrcSizeAppend(flagsInfo.pBindingFlags[bindingIdx], hash);
}
}
break;
default:
assert(false); // missing struct here, bad
break;
}
return calcVkStructHash(inStruct->pNext, hash);
}
#endif
vk::SampleCountFlagBits samplesToVk(unsigned samples) noexcept
{
switch (samples)
{
case 1:
return vk::SampleCountFlagBits::e1;
case 2:
return vk::SampleCountFlagBits::e2;
case 4:
return vk::SampleCountFlagBits::e4;
case 8:
return vk::SampleCountFlagBits::e8;
case 16:
return vk::SampleCountFlagBits::e16;
case 32:
return vk::SampleCountFlagBits::e32;
case 64:
return vk::SampleCountFlagBits::e64;
default:
logAndDie("Invalid sample count: {}.", samples);
}
}
vk::Format detectDepthBufferFormat(Device& device, unsigned samples) noexcept
{
const vk::SampleCountFlagBits sampleCount = samplesToVk(samples);
for (const vk::Format depthFormat : DEPTH_FORMATS)
{
try
{
const vk::ImageFormatProperties props = device.getVkPhysicalDevice().getImageFormatProperties(depthFormat, vk::ImageType::e2D, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment);
if (props.sampleCounts & sampleCount) {
return depthFormat;
}
}
catch(vk::FormatNotSupportedError&)
{
continue; // not supported
}
}
return vk::Format::eUndefined;
}
std::vector<unsigned> detectSupportedSampleCounts(Device& device) noexcept
{
std::vector<unsigned> result = {1};
for (const unsigned samples : {2, 4, 8, 16, 32, 64})
{
if (detectDepthBufferFormat(device, samples) != vk::Format::eUndefined) {
result.push_back(samples);
}
}
return result;
}
} // namespace iwa

319
source/window.cpp Normal file
View File

@@ -0,0 +1,319 @@
#include "iwa/window.hpp"
#include <SDL_vulkan.h>
#include "iwa/log.hpp"
#include "iwa/instance.hpp"
namespace iwa
{
inline constexpr const char* WINDOW_DATA_NAME = "iwa_window";
namespace
{
Window* gLastEventWindow = nullptr;
Window* getWindowFromEvent(Uint32 windowID) noexcept
{
SDL_Window* sdlWindow = SDL_GetWindowFromID(windowID);
Window* iwaWindow = static_cast<Window*>(SDL_GetWindowData(sdlWindow, WINDOW_DATA_NAME));
if (iwaWindow != nullptr)
{
gLastEventWindow = iwaWindow;
}
return gLastEventWindow;
}
void handleWindowEvent(const SDL_Event& event)
{
Window* iwaWindow = getWindowFromEvent(event.window.windowID);
if (iwaWindow == nullptr) {
return;
}
switch (event.window.event)
{
case SDL_WINDOWEVENT_FOCUS_GAINED:
iwaWindow->focusGained.emit();
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
iwaWindow->focusLost.emit();
break;
case SDL_WINDOWEVENT_ENTER:
iwaWindow->mouseEntered.emit();
break;
case SDL_WINDOWEVENT_LEAVE:
iwaWindow->mouseLeft.emit();
break;
case SDL_WINDOWEVENT_CLOSE:
iwaWindow->closeRequested.emit();
break;
}
}
void handleKeyEvent(const SDL_Event& sdlEvent)
{
Window* iwaWindow = getWindowFromEvent(sdlEvent.key.windowID);
if (iwaWindow == nullptr) {
return;
}
const KeyEvent event = {
.keyCode = static_cast<KeyCode>(sdlEvent.key.keysym.sym),
.scanCode = static_cast<ScanCode>(sdlEvent.key.keysym.scancode),
.modifiers = {
.leftShift = (sdlEvent.key.keysym.mod & KMOD_LSHIFT) != 0,
.rightShift = (sdlEvent.key.keysym.mod & KMOD_RSHIFT) != 0,
.leftCtrl = (sdlEvent.key.keysym.mod & KMOD_LCTRL) != 0,
.rightCtrl = (sdlEvent.key.keysym.mod & KMOD_RCTRL) != 0,
.leftAlt = (sdlEvent.key.keysym.mod & KMOD_LALT) != 0,
.rightAlt = (sdlEvent.key.keysym.mod & KMOD_RALT) != 0,
.leftMeta = (sdlEvent.key.keysym.mod & KMOD_LGUI) != 0,
.rightMeta = (sdlEvent.key.keysym.mod & KMOD_RGUI) != 0,
},
.down = sdlEvent.type == SDL_KEYDOWN,
.repeat = sdlEvent.key.repeat > 0
};
iwaWindow->keyChanged.emit(event);
}
void handleMouseMotion(const SDL_Event& sdlEvent)
{
Window* iwaWindow = getWindowFromEvent(sdlEvent.motion.windowID);
if (iwaWindow == nullptr) {
return;
}
const MouseMoveEvent event = {
.relativeX = sdlEvent.motion.xrel,
.relativeY = sdlEvent.motion.yrel,
.absoluteX = sdlEvent.motion.x,
.absoluteY = sdlEvent.motion.y,
.warped = false // TODO?
};
iwaWindow->mouseMoved.emit(event);
}
void handleMouseButtonEvent(const SDL_Event& sdlEvent)
{
Window* iwaWindow = getWindowFromEvent(sdlEvent.button.windowID);
if (iwaWindow == nullptr) {
return;
}
const MouseButtonEvent event = {
.button = static_cast<MouseButton>(sdlEvent.button.button),
.clicks = sdlEvent.button.clicks,
.down = sdlEvent.type == SDL_MOUSEBUTTONDOWN
};
iwaWindow->mouseButtonChanged.emit(event);
}
void handleMouseWheelEvent(const SDL_Event& sdlEvent)
{
Window* iwaWindow = getWindowFromEvent(sdlEvent.wheel.windowID);
if (iwaWindow == nullptr) {
return;
}
const MouseWheelEvent event = {
.relativeX = sdlEvent.wheel.x,
.relativeY = sdlEvent.wheel.y
};
iwaWindow->mouseScrolled.emit(event);
}
void handleTextInputEvent(const SDL_Event& sdlEvent)
{
Window* iwaWindow = getWindowFromEvent(sdlEvent.text.windowID);
if (iwaWindow == nullptr) {
return;
}
const TextInputEvent event = {
.text = sdlEvent.text.text
};
iwaWindow->textEntered.emit(event);
}
void handleEvent(const SDL_Event& event)
{
switch (event.type)
{
case SDL_WINDOWEVENT:
handleWindowEvent(event);
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
handleKeyEvent(event);
break;
case SDL_MOUSEMOTION:
handleMouseMotion(event);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
handleMouseButtonEvent(event);
break;
case SDL_MOUSEWHEEL:
handleMouseWheelEvent(event);
break;
case SDL_TEXTINPUT:
handleTextInputEvent(event);
break;
}
}
mijin::Task<> c_sdlLoop(ObjectPtr<Instance> instance)
{
while (!instance->isQuitRequested())
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
handleEvent(event);
}
co_await mijin::c_suspend();
}
// SDL_Quit();
co_return;
}
void initSDL(Instance& instance)
{
static bool sdlInited = false;
if (sdlInited) {
return;
}
sdlInited = true;
if (SDL_Init(0) != 0)
{
logAndDie("Error initializing SDL: {}.", SDL_GetError());
}
SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0");
instance.getMainTaskLoop().addTask(c_sdlLoop(instance.getPointer()));
}
}
Window::Window(ObjectPtr<Instance> owner, const WindowCreationArgs& args) : super_t(std::move(owner))
{
initSDL(*getOwner());
const Uint32 flags = SDL_WINDOW_VULKAN
| (args.flags.hidden ? SDL_WINDOW_HIDDEN : 0)
| (args.flags.resizable ? SDL_WINDOW_RESIZABLE : 0)
| (args.flags.borderless ? SDL_WINDOW_BORDERLESS : 0)
| (args.flags.alwayOnTop ? SDL_WINDOW_ALWAYS_ON_TOP : 0)
| (args.flags.skipTaskbar ? SDL_WINDOW_UTILITY : 0);
mHandle = SDL_CreateWindow(
/* title = */ args.title.c_str(),
/* x = */ SDL_WINDOWPOS_CENTERED,
/* y = */ SDL_WINDOWPOS_CENTERED,
/* w = */ args.width,
/* h = */ args.height,
/* flags = */ flags
);
if (mHandle == nullptr)
{
logAndDie("Error creating SDL window: {}.", SDL_GetError());
}
SDL_SetWindowData(mHandle, WINDOW_DATA_NAME, this);
VkSurfaceKHR surface = VK_NULL_HANDLE;
if (!SDL_Vulkan_CreateSurface(mHandle, getOwner()->getVkHandle(), &surface))
{
logAndDie("Error creating Vulkan surface for SDL window: {}", SDL_GetError());
}
mSurface = surface;
getOwner()->windowCreated.emit(*this);
}
Window::~Window() noexcept
{
getOwner()->queueDelete([surface=mSurface, handle=mHandle, instance= getOwner()]
{
if (surface)
{
instance->getVkHandle().destroySurfaceKHR(surface);
}
if (handle)
{
SDL_DestroyWindow(handle);
}
});
}
bool Window::isVisible() const noexcept
{
return (SDL_GetWindowFlags(mHandle) & SDL_WINDOW_HIDDEN) == 0;
}
void Window::setVisible(bool visible) noexcept
{
if (visible) {
SDL_ShowWindow(mHandle);
}
else {
SDL_HideWindow(mHandle);
}
}
std::pair<int, int> Window::getSize() const noexcept
{
std::pair<int, int> size;
SDL_GetWindowSize(mHandle, &size.first, &size.second);
return size;
}
void Window::setSize(int width, int height) noexcept
{
SDL_SetWindowSize(mHandle, std::max(width, 1), std::max(height, 1));
}
std::pair<int, int> Window::getPosition() const noexcept
{
std::pair<int, int> position;
SDL_GetWindowPosition(mHandle, &position.first, &position.second);
return position;
}
void Window::setPosition(int xPos, int yPos) noexcept
{
SDL_SetWindowPosition(mHandle, xPos, yPos);
}
WindowBorder Window::getWindowBorder() const noexcept
{
WindowBorder windowBorder;
SDL_GetWindowBordersSize(mHandle, &windowBorder.top, &windowBorder.left, &windowBorder.bottom, &windowBorder.right);
return windowBorder;
}
bool Window::isFocused() const noexcept
{
return (SDL_GetWindowFlags(mHandle) & SDL_WINDOW_INPUT_FOCUS) != 0;
}
void Window::focus() noexcept
{
SDL_RaiseWindow(mHandle);
}
void Window::setMouseMode(MouseMode mouseMode) noexcept
{
switch (mouseMode)
{
case MouseMode::NORMAL:
SDL_SetRelativeMouseMode(SDL_FALSE);
SDL_SetWindowMouseGrab(mHandle, SDL_FALSE);
break;
case MouseMode::CAPTURED:
SDL_SetRelativeMouseMode(SDL_TRUE);
SDL_SetWindowMouseGrab(mHandle, SDL_TRUE);
break;
}
}
void Window::setModalFor(mijin::Optional<const Window&> parent) noexcept
{
SDL_SetWindowModalFor(mHandle, parent.empty() ? nullptr : parent->getSDLWindow());
}
}