From a17039d87bc452c51223bede80557134594955d1 Mon Sep 17 00:00:00 2001 From: Attila Uygun Date: Tue, 31 Oct 2023 21:40:07 +0100 Subject: [PATCH] Add Renderer::UpdateGeometry() Also add its implementations in Vulkan and OpenGL renderers. Use it in ImguiBackend instead of recreating geometry buffers every frame. --- src/engine/asset/mesh.cc | 18 +--- src/engine/engine.cc | 99 +++++++++++++++++++ src/engine/imgui_backend.cc | 17 ++-- src/engine/renderer/opengl/renderer_opengl.cc | 64 ++++++++---- src/engine/renderer/opengl/renderer_opengl.h | 4 + src/engine/renderer/renderer.h | 6 ++ src/engine/renderer/renderer_types.cc | 21 ++++ src/engine/renderer/renderer_types.h | 3 + src/engine/renderer/vulkan/renderer_vulkan.cc | 70 ++++++++----- src/engine/renderer/vulkan/renderer_vulkan.h | 8 +- 10 files changed, 249 insertions(+), 61 deletions(-) diff --git a/src/engine/asset/mesh.cc b/src/engine/asset/mesh.cc index 0e9821d..f6d36ab 100644 --- a/src/engine/asset/mesh.cc +++ b/src/engine/asset/mesh.cc @@ -139,28 +139,16 @@ bool Mesh::Load(const std::string& file_name) { } } } + // TODO: Load indices return true; } size_t Mesh::GetVertexSize() const { - unsigned int size = 0; - for (auto& attr : vertex_description_) { - size += std::get<2>(attr) * std::get<3>(attr); - } - return size; + return eng::GetVertexSize(vertex_description_); } size_t Mesh::GetIndexSize() const { - switch (index_description_) { - case kDataType_Byte: - return sizeof(char); - case kDataType_UShort: - return sizeof(unsigned short); - case kDataType_UInt: - return sizeof(unsigned int); - default: - return 0; - } + return eng::GetIndexSize(index_description_); } } // namespace eng diff --git a/src/engine/engine.cc b/src/engine/engine.cc index d760062..057fe2d 100644 --- a/src/engine/engine.cc +++ b/src/engine/engine.cc @@ -144,6 +144,105 @@ void Engine::Update(float delta_time) { if (stats_visible_) ShowStats(); + ImGui::ShowDemoWindow(); +#if 0 + // ImVec2 center = ImGui::GetMainViewport()->GetCenter(); + // ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always, ImVec2(0, 0)); + + static ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings; + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->Pos); + ImGui::SetNextWindowSize(viewport->Size); + ImGui::Begin("Stats", nullptr, window_flags); + + static ImGuiTableFlags flags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; + + // // PushStyleCompact(); + // ImGuiStyle& style = ImGui::GetStyle(); + // ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x, (float)(int)(style.FramePadding.y * 0.60f))); + // ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, (float)(int)(style.ItemSpacing.y * 0.60f))); + + // ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", &flags, ImGuiTableFlags_ScrollY); + + // // PopStyleCompact(); + // ImGui::PopStyleVar(2); + + // When using ScrollX or ScrollY we need to specify a size for our table container! + // Otherwise by default the table will fit all available space, like a BeginChild() call. + // ImVec2 outer_size = ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * 8); + if (ImGui::BeginTable("table_scrolly", 3, flags)) + { + ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible + ImGui::TableSetupColumn("One", ImGuiTableColumnFlags_None); + ImGui::TableSetupColumn("Two", ImGuiTableColumnFlags_None); + ImGui::TableSetupColumn("Three", ImGuiTableColumnFlags_None); + ImGui::TableHeadersRow(); + + // Demonstrate using clipper for large vertical lists + ImGuiListClipper clipper; + clipper.Begin(1000); + while (clipper.Step()) + { + for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; row++) + { + ImGui::TableNextRow(); + // ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap; + for (int column = 0; column < 3; column++) + { + char buf[32]; + sprintf(buf, "Hello %d,%d", column, row); + ImGui::TableSetColumnIndex(column); + ImGui::Selectable(buf, false, ImGuiSelectableFlags_SpanAllColumns); + // ImGui::Text("Hello %d,%d", column, row); + } + + // static int row_bg_target = 1; + // static int row_bg_type = 1; + // if (ImGui::TableGetColumnFlags() & ImGuiTableColumnFlags_IsHovered) { + // ImU32 row_bg_color = ImGui::GetColorU32(row_bg_type == 1 ? ImVec4(0.7f, 0.3f, 0.3f, 0.65f) : ImVec4(0.2f + row * 0.1f, 0.2f, 0.2f, 0.65f)); // Flat or Gradient? + // ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0 + row_bg_target, row_bg_color); + // // ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, IM_COL32(0, 100, 0, 255)); + // } + } + } + ImGui::EndTable(); + } + ImGui::End(); + +#else + +{ + bool show_app_fullscreen = false; + static bool use_work_area = true; + static ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings; + + // We demonstrate using the full viewport area or the work area (without menu-bars, task-bars etc.) + // Based on your use case you may want one or the other. + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(use_work_area ? viewport->WorkPos : viewport->Pos); + ImGui::SetNextWindowSize(use_work_area ? viewport->WorkSize : viewport->Size); + + if (ImGui::Begin("Example: Fullscreen window", &show_app_fullscreen, flags)) + { + ImGui::Checkbox("Use work area instead of main area", &use_work_area); + ImGui::SameLine(); + // HelpMarker("Main Area = entire viewport,\nWork Area = entire viewport minus sections used by the main menu bars, task bars etc.\n\nEnable the main-menu bar in Examples menu to see the difference."); + + ImGui::CheckboxFlags("ImGuiWindowFlags_NoBackground", &flags, ImGuiWindowFlags_NoBackground); + ImGui::CheckboxFlags("ImGuiWindowFlags_NoDecoration", &flags, ImGuiWindowFlags_NoDecoration); + ImGui::Indent(); + ImGui::CheckboxFlags("ImGuiWindowFlags_NoTitleBar", &flags, ImGuiWindowFlags_NoTitleBar); + ImGui::CheckboxFlags("ImGuiWindowFlags_NoCollapse", &flags, ImGuiWindowFlags_NoCollapse); + ImGui::CheckboxFlags("ImGuiWindowFlags_NoScrollbar", &flags, ImGuiWindowFlags_NoScrollbar); + ImGui::Unindent(); + + if (ImGui::Button("Close this window")) + show_app_fullscreen = false; + } + ImGui::End(); +} +#endif + imgui_backend_.Render(); } diff --git a/src/engine/imgui_backend.cc b/src/engine/imgui_backend.cc index a0211bd..431f3ea 100644 --- a/src/engine/imgui_backend.cc +++ b/src/engine/imgui_backend.cc @@ -68,6 +68,9 @@ void ImguiBackend::Initialize() { } void ImguiBackend::Shutdown() { + for (auto& id : geometries_) + renderer_->DestroyGeometry(id); + geometries_.clear(); ImGui::DestroyContext(); shader_.reset(); } @@ -75,6 +78,7 @@ void ImguiBackend::Shutdown() { void ImguiBackend::CreateRenderResources(Renderer* renderer) { renderer_ = renderer; shader_->SetRenderer(renderer); + geometries_.clear(); auto source = std::make_unique(); if (source->Load("engine/imgui.glsl")) { @@ -115,10 +119,6 @@ std::unique_ptr ImguiBackend::OnInputEvent( } void ImguiBackend::NewFrame(float delta_time) { - for (auto& id : geometries_) - renderer_->DestroyGeometry(id); - geometries_.clear(); - ImGuiIO& io = ImGui::GetIO(); io.DisplaySize = ImVec2((float)renderer_->GetScreenWidth(), (float)renderer_->GetScreenHeight()); @@ -129,7 +129,8 @@ void ImguiBackend::NewFrame(float delta_time) { void ImguiBackend::Render() { ImGui::Render(); ImDrawData* draw_data = ImGui::GetDrawData(); - geometries_.resize(draw_data->CmdListsCount); + if (geometries_.size() < draw_data->CmdListsCount) + geometries_.resize(draw_data->CmdListsCount, 0); for (int n = 0; n < draw_data->CmdListsCount; n++) { const ImDrawList* cmd_list = draw_data->CmdLists[n]; auto mesh = std::make_unique(); @@ -137,7 +138,11 @@ void ImguiBackend::Render() { cmd_list->VtxBuffer.Size, cmd_list->VtxBuffer.Data, kDataType_UShort, cmd_list->IdxBuffer.Size, cmd_list->IdxBuffer.Data); - geometries_[n] = renderer_->CreateGeometry(std::move(mesh)); + if (geometries_[n] == 0) + geometries_[n] = renderer_->CreateGeometry(mesh->primitive(), + mesh->vertex_description(), + mesh->index_description()); + renderer_->UpdateGeometry(geometries_[n], std::move(mesh)); } } diff --git a/src/engine/renderer/opengl/renderer_opengl.cc b/src/engine/renderer/opengl/renderer_opengl.cc index adbd59b..148d30a 100644 --- a/src/engine/renderer/opengl/renderer_opengl.cc +++ b/src/engine/renderer/opengl/renderer_opengl.cc @@ -72,11 +72,21 @@ void RendererOpenGL::ResetScissor() { } uint64_t RendererOpenGL::CreateGeometry(std::unique_ptr mesh) { + auto id = CreateGeometry(mesh->primitive(), mesh->vertex_description(), + mesh->index_description()); + if (id != kInvalidId) + UpdateGeometry(id, std::move(mesh)); + return id; +} + +uint64_t RendererOpenGL::CreateGeometry(Primitive primitive, + VertexDescription vertex_description, + DataType index_description) { // Verify that we have a valid layout and get the total byte size per vertex. - GLuint vertex_size = mesh->GetVertexSize(); + GLuint vertex_size = GetVertexSize(vertex_description); if (!vertex_size) { LOG(0) << "Invalid vertex layout"; - return 0; + return kInvalidId; } GLuint vertex_array_id = 0; @@ -85,30 +95,25 @@ uint64_t RendererOpenGL::CreateGeometry(std::unique_ptr mesh) { glBindVertexArray(vertex_array_id); } - // Create the vertex buffer and upload the data. + // Create the vertex buffer. GLuint vertex_buffer_id = 0; glGenBuffers(1, &vertex_buffer_id); glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_id); - glBufferData(GL_ARRAY_BUFFER, mesh->num_vertices() * vertex_size, - mesh->GetVertices(), GL_STATIC_DRAW); // Make sure the vertex format is understood and the attribute pointers are // set up. std::vector vertex_layout; - if (!SetupVertexLayout(mesh->vertex_description(), vertex_size, - vertex_array_objects_, vertex_layout)) { + if (!SetupVertexLayout(vertex_description, vertex_size, vertex_array_objects_, + vertex_layout)) { LOG(0) << "Invalid vertex layout"; - return 0; + return kInvalidId; } - // Create the index buffer and upload the data. + // Create the index buffer. GLuint index_buffer_id = 0; - if (mesh->GetIndices()) { + if (index_description != kDataType_Invalid) { glGenBuffers(1, &index_buffer_id); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer_id); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, - mesh->num_indices() * mesh->GetIndexSize(), mesh->GetIndices(), - GL_STATIC_DRAW); } if (vertex_array_id) { @@ -121,10 +126,10 @@ uint64_t RendererOpenGL::CreateGeometry(std::unique_ptr mesh) { } uint64_t resource_id = ++last_resource_id_; - geometries_[resource_id] = {(GLsizei)mesh->num_vertices(), - (GLsizei)mesh->num_indices(), - kGlPrimitive[mesh->primitive()], - kGlDataType[mesh->index_description()], + geometries_[resource_id] = {0, + 0, + kGlPrimitive[primitive], + kGlDataType[index_description], vertex_layout, vertex_size, vertex_array_id, @@ -133,6 +138,31 @@ uint64_t RendererOpenGL::CreateGeometry(std::unique_ptr mesh) { return resource_id; } +void RendererOpenGL::UpdateGeometry(uint64_t resource_id, + std::unique_ptr mesh) { + auto it = geometries_.find(resource_id); + if (it == geometries_.end()) + return; + + // Go with GL_STATIC_DRAW for the first update. + GLenum usage = it->second.num_vertices > 0 ? GL_STREAM_DRAW : GL_STATIC_DRAW; + + // Upload the vertex data. + glBindBuffer(GL_ARRAY_BUFFER, it->second.vertex_buffer_id); + glBufferData(GL_ARRAY_BUFFER, mesh->num_vertices() * it->second.vertex_size, + mesh->GetVertices(), usage); + it->second.num_vertices = (GLsizei)mesh->num_vertices(); + + // Upload the index data. + if (it->second.index_buffer_id) { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, it->second.index_buffer_id); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, + mesh->num_indices() * mesh->GetIndexSize(), mesh->GetIndices(), + usage); + it->second.num_indices = (GLsizei)mesh->num_indices(); + } +} + void RendererOpenGL::DestroyGeometry(uint64_t resource_id) { auto it = geometries_.find(resource_id); if (it == geometries_.end()) diff --git a/src/engine/renderer/opengl/renderer_opengl.h b/src/engine/renderer/opengl/renderer_opengl.h index ce9e356..d032aab 100644 --- a/src/engine/renderer/opengl/renderer_opengl.h +++ b/src/engine/renderer/opengl/renderer_opengl.h @@ -39,6 +39,10 @@ class RendererOpenGL final : public Renderer { void ResetScissor() final; uint64_t CreateGeometry(std::unique_ptr mesh) final; + uint64_t CreateGeometry(Primitive primitive, + VertexDescription vertex_description, + DataType index_description = kDataType_Invalid) final; + void UpdateGeometry(uint64_t resource_id, std::unique_ptr mesh) final; void DestroyGeometry(uint64_t resource_id) final; void Draw(uint64_t resource_id, uint64_t num_indices = 0, diff --git a/src/engine/renderer/renderer.h b/src/engine/renderer/renderer.h index 93f336b..30ca986 100644 --- a/src/engine/renderer/renderer.h +++ b/src/engine/renderer/renderer.h @@ -46,6 +46,12 @@ class Renderer { virtual void ResetScissor() = 0; virtual uint64_t CreateGeometry(std::unique_ptr mesh) = 0; + virtual uint64_t CreateGeometry( + Primitive primitive, + VertexDescription vertex_description, + DataType index_description = kDataType_Invalid) = 0; + virtual void UpdateGeometry(uint64_t resource_id, + std::unique_ptr mesh) = 0; virtual void DestroyGeometry(uint64_t resource_id) = 0; virtual void Draw(uint64_t resource_id, uint64_t num_indices = 0, diff --git a/src/engine/renderer/renderer_types.cc b/src/engine/renderer/renderer_types.cc index e0a64f3..8f4d78d 100644 --- a/src/engine/renderer/renderer_types.cc +++ b/src/engine/renderer/renderer_types.cc @@ -14,6 +14,27 @@ const char kLayoutDelimiter[] = ";/ \t"; namespace eng { +size_t GetVertexSize(const VertexDescription& vertex_description) { + size_t size = 0; + for (auto& attr : vertex_description) { + size += std::get<2>(attr) * std::get<3>(attr); + } + return size; +} + +size_t GetIndexSize(DataType index_description) { + switch (index_description) { + case kDataType_Byte: + return sizeof(char); + case kDataType_UShort: + return sizeof(unsigned short); + case kDataType_UInt: + return sizeof(unsigned int); + default: + return 0; + } +} + bool ParseVertexDescription(const std::string& vd_str, VertexDescription& out) { // Parse the description. char buffer[32]; diff --git a/src/engine/renderer/renderer_types.h b/src/engine/renderer/renderer_types.h index 056075a..a3664e3 100644 --- a/src/engine/renderer/renderer_types.h +++ b/src/engine/renderer/renderer_types.h @@ -41,6 +41,9 @@ using DataTypeSize = size_t; using VertexDescription = std::vector>; +size_t GetVertexSize(const VertexDescription& vertex_description); +size_t GetIndexSize(DataType index_description); + bool ParseVertexDescription(const std::string& vd_str, VertexDescription& out); } // namespace eng diff --git a/src/engine/renderer/vulkan/renderer_vulkan.cc b/src/engine/renderer/vulkan/renderer_vulkan.cc index 1a155ba..837bb9f 100644 --- a/src/engine/renderer/vulkan/renderer_vulkan.cc +++ b/src/engine/renderer/vulkan/renderer_vulkan.cc @@ -307,6 +307,8 @@ VertexInputDescription GetVertexInputDescription( VkIndexType GetIndexType(eng::DataType data_type) { switch (data_type) { + case eng::kDataType_Invalid: + return VK_INDEX_TYPE_NONE_KHR; case eng::kDataType_UInt: return VK_INDEX_TYPE_UINT32; case eng::kDataType_UShort: @@ -315,7 +317,7 @@ VkIndexType GetIndexType(eng::DataType data_type) { break; } NOTREACHED() << "Invalid index type: " << data_type; - return VK_INDEX_TYPE_UINT16; + return VK_INDEX_TYPE_NONE_KHR; } VkFormat GetImageFormat(eng::Image::Format format) { @@ -442,47 +444,71 @@ void RendererVulkan::ResetScissor() { } uint64_t RendererVulkan::CreateGeometry(std::unique_ptr mesh) { - auto& geometry = geometries_[++last_resource_id_] = {}; + auto id = CreateGeometry(mesh->primitive(), mesh->vertex_description(), + mesh->index_description()); + if (id != kInvalidId) + UpdateGeometry(id, std::move(mesh)); + return id; +} - geometry.num_vertices = mesh->num_vertices(); - size_t vertex_data_size = mesh->GetVertexSize() * geometry.num_vertices; +uint64_t RendererVulkan::CreateGeometry(Primitive primitive, + VertexDescription vertex_description, + DataType index_description) { + auto& geometry = geometries_[++last_resource_id_] = {}; + geometry.vertex_size = GetVertexSize(vertex_description); + geometry.index_type = GetIndexType(index_description); + geometry.index_type_size = GetIndexSize(index_description); + return last_resource_id_; +} + +void RendererVulkan::UpdateGeometry(uint64_t resource_id, + std::unique_ptr mesh) { + auto it = geometries_.find(resource_id); + if (it == geometries_.end()) + return; + + it->second.num_vertices = mesh->num_vertices(); + size_t vertex_data_size = it->second.vertex_size * it->second.num_vertices; size_t index_data_size = 0; if (mesh->GetIndices()) { - geometry.num_indices = mesh->num_indices(); - geometry.index_type = GetIndexType(mesh->index_description()); - geometry.index_type_size = mesh->GetIndexSize(); - index_data_size = geometry.index_type_size * geometry.num_indices; + DCHECK(it->second.index_type != VK_INDEX_TYPE_NONE_KHR); + it->second.num_indices = mesh->num_indices(); + index_data_size = it->second.index_type_size * it->second.num_indices; } - size_t data_size = vertex_data_size + index_data_size; - AllocateBuffer(geometry.buffer, data_size, - VK_BUFFER_USAGE_TRANSFER_SRC_BIT | - VK_BUFFER_USAGE_TRANSFER_DST_BIT | - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, - VMA_MEMORY_USAGE_GPU_ONLY); + size_t data_size = vertex_data_size + index_data_size; + if (it->second.buffer_size < data_size) { + DLOG(1) << __func__ << "Reallocate buffer " << data_size; + if (it->second.buffer_size > 0) + FreeBuffer(std::move(it->second.buffer)); + AllocateBuffer(it->second.buffer, data_size, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT | + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + VMA_MEMORY_USAGE_GPU_ONLY); + it->second.buffer_size = data_size; + } task_runner_.PostTask(HERE, std::bind(&RendererVulkan::UpdateBuffer, this, - std::get<0>(geometry.buffer), 0, + std::get<0>(it->second.buffer), 0, mesh->GetVertices(), vertex_data_size)); - if (geometry.num_indices > 0) { - geometry.index_data_offset = vertex_data_size; + if (it->second.num_indices > 0) { + it->second.index_data_offset = vertex_data_size; task_runner_.PostTask(HERE, std::bind(&RendererVulkan::UpdateBuffer, this, - std::get<0>(geometry.buffer), - geometry.index_data_offset, + std::get<0>(it->second.buffer), + it->second.index_data_offset, mesh->GetIndices(), index_data_size)); } task_runner_.PostTask(HERE, std::bind(&RendererVulkan::BufferMemoryBarrier, this, - std::get<0>(geometry.buffer), 0, data_size, + std::get<0>(it->second.buffer), 0, data_size, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT)); task_runner_.Delete(HERE, std::move(mesh)); semaphore_.release(); - - return last_resource_id_; } void RendererVulkan::DestroyGeometry(uint64_t resource_id) { diff --git a/src/engine/renderer/vulkan/renderer_vulkan.h b/src/engine/renderer/vulkan/renderer_vulkan.h index 7cfeef0..1d53812 100644 --- a/src/engine/renderer/vulkan/renderer_vulkan.h +++ b/src/engine/renderer/vulkan/renderer_vulkan.h @@ -40,6 +40,10 @@ class RendererVulkan final : public Renderer { void ResetScissor() final; uint64_t CreateGeometry(std::unique_ptr mesh) final; + uint64_t CreateGeometry(Primitive primitive, + VertexDescription vertex_description, + DataType index_description = kDataType_Invalid) final; + void UpdateGeometry(uint64_t resource_id, std::unique_ptr mesh) final; void DestroyGeometry(uint64_t resource_id) final; void Draw(uint64_t resource_id, uint64_t num_indices = 0, @@ -107,11 +111,13 @@ class RendererVulkan final : public Renderer { struct GeometryVulkan { Buffer buffer; + size_t buffer_size = 0; uint32_t num_vertices = 0; uint32_t num_indices = 0; + size_t vertex_size = 0; uint64_t index_data_offset = 0; uint64_t index_type_size = 0; - VkIndexType index_type = VK_INDEX_TYPE_UINT16; + VkIndexType index_type = VK_INDEX_TYPE_NONE_KHR; }; struct ShaderVulkan {