Squash improve-ide-compat into main

Fixes GH-5

- cppcheck replaced with clang-tidy
- clang-tidy lint warnings fixed
- Reworked build tools from scratch to make IDE setup easier
- Added 1.5 IDE setup guides
This commit is contained in:
OLEGSHA 2023-11-10 21:30:55 +01:00
parent 189d19fd4a
commit ae4e265a90
No known key found for this signature in database
GPG Key ID: 4BB0CC24939DB485
66 changed files with 2017 additions and 1498 deletions

5
.clang-format Normal file
View File

@ -0,0 +1,5 @@
{
"BasedOnStyle": "LLVM",
"IndentWidth": 4,
"CommentPragmas": "NOLINT",
}

78
.clang-tidy Normal file
View File

@ -0,0 +1,78 @@
Checks: "-*,\
clang-analyzer-*,\
cppcoreguidelines-*,\
modernize-*,\
performance-*,\
readability-*,\
clang-diagnostic-*,\
-modernize-use-trailing-return-type,\
-readability-implicit-bool-conversion,\
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,\
-cppcoreguidelines-pro-type-reinterpret-cast,\
-cppcoreguidelines-pro-bounds-constant-array-index,\
-*-avoid-c-arrays,\
-readability-else-after-return,\
-readability-named-parameter,\
-readability-use-anyofallof,\
-cppcoreguidelines-pro-bounds-pointer-arithmetic,\
-performance-trivially-destructible,\
-modernize-make-unique,\
-cppcoreguidelines-prefer-member-initializer,\
-*-magic-numbers,\
-readability-suspicious-call-argument,\
-cppcoreguidelines-pro-type-union-access"
# modernize-use-trailing-return-type
# ignore reason: reduces readability
# readability-implicit-bool-conversion
# ignore reason: expected use by C libraries (GLFW, Vulkan API)
# cppcoreguidelines-pro-bounds-array-to-pointer-decay
# ignore reason: expected use by C libraries
# cppcoreguidelines-pro-type-reinterpret-cast
# ignore reason: expected use by C libraries
# cppcoreguidelines-pro-bounds-constant-array-index
# ignore reason: infeasible to avoid without GSL
# *-avoid-c-arrays
# ignore reason: often makes code significantly more verbose
# readability-else-after-return
# ignore reason: personal preference of OLEGSHA (using 'else' helps highlight
# branches in code)
# readability-named-parameter
# ignore reason: using GCC convention is clear enough
# readability-use-anyofallof
# ignore reason: these relatively obscure functions reduce readability by
# increasing code complexity and verbosity
# cppcoreguidelines-pro-bounds-pointer-arithmetic
# ignore reason: infeasible to avoid in C++17
# performance-trivially-destructible
# ignore reason: breaks incapsulation too often
# modernize-make-unique
# ignore reason: impossible with brace-init lists:
# struct S { int a; int b; };
# std::unique_ptr<S>(new S { 1, 2 }) // works
# std::make_unique<S>(1, 2) // error
# std::make_unique<S>({1, 2}) // error
# cppcoreguidelines-prefer-member-initializer
# ignore reason: rule fails to notice execution order dependencies
# *-magic-numbers
# ignore reason: triggers in many trivial cases (e.g. 6 sides of a cube);
# infeasible to avoid while writing placeholder code
# readability-suspicious-call-argument
# ignore reason: trips up on geometry code (y no NOLINTBEGIN, Debian?)
# cppcoreguidelines-pro-type-union-access
# ignore reason: triggers on GLM code

12
.gitignore vendored
View File

@ -4,11 +4,19 @@ build
# Run directory
run
# Local environment setup file
tools/private.sh
# Local settings for pre-commit.py
tools/pre-commit-settings.json
# Prevent anyone from accidentally uploading CMakeFiles
CMakeFiles
# Visual Studio garbage
.vs
CMakeSettings.json
out
# Some weirdos use Kate
*.kate-swp
# Real creeps use KDevelop
*.kdev*

View File

@ -1,11 +1,37 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.12)
project(progressia)
set(VERSION "0.0.1")
add_executable(progressia)
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/tools/cmake")
include(embed)
# Options
add_executable(progressia
option(DEV_MODE "Enable additional functionality required for development.")
string(CONCAT BUILD_ID_expl
"Build ID or \"dev\".\n"
"Set to a unique identifying string if you intend to publish your builds.")
set(BUILD_ID "dev" CACHE STRING "${BUILD_ID_expl}")
string(CONCAT VULKAN_ERROR_CHECKING_expl
"Enable Vulkan validation layers to detect Vulkan API usage errors "
"at runtime.\n"
"Requires Vulkan SDK. This will lead to decreased performance.")
option(VULKAN_ERROR_CHECKING "${VULKAN_ERROR_CHECKING_expl}")
# Tools
set(tools ${PROJECT_SOURCE_DIR}/tools)
set(generated ${PROJECT_BINARY_DIR}/generated)
file(MAKE_DIRECTORY "${generated}")
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/tools/")
include(embed/embed)
include(glslc)
include(dev-mode)
# Source files
target_sources(progressia PRIVATE
desktop/main.cpp
desktop/graphics/glfw_mgmt.cpp
desktop/graphics/vulkan_common.cpp
@ -19,6 +45,7 @@ add_executable(progressia
desktop/graphics/vulkan_texture_descriptors.cpp
desktop/graphics/vulkan_adapter.cpp
desktop/graphics/vulkan_swap_chain.cpp
desktop/graphics/vulkan_physical_device.cpp
main/game.cpp
main/logging.cpp
@ -26,34 +53,78 @@ add_executable(progressia
main/rendering/image.cpp
main/stb_image.c
${generated}/embedded_resources.cpp
${generated}/embedded_resources/embedded_resources.cpp
)
target_include_directories(progressia PRIVATE ${generated})
# Embedded resources
target_glsl_shaders(progressia
desktop/graphics/shaders/shader.frag
desktop/graphics/shaders/shader.vert)
# Do Windows-specific tweaks
if (WIN32)
set_target_properties(progressia PROPERTIES WIN32_EXECUTABLE true)
target_link_options(progressia PRIVATE -static-libstdc++ -static-libgcc)
endif()
target_embeds(progressia
assets/texture.png
assets/texture2.png)
compile_glsl(progressia)
compile_embeds(progressia)
target_include_directories(progressia PRIVATE ${generated}/embedded_resources)
# Compilation settings
set_property(TARGET progressia PROPERTY CXX_STANDARD 17)
target_compile_options(progressia PRIVATE -Wall -Wextra -Wpedantic -Werror)
# Version information
if (NOT DEFINED BUILD_ID)
set(BUILD_ID "dev")
set_property(TARGET progressia PROPERTY CXX_STANDARD 17)
set_property(TARGET progressia PROPERTY CXX_STANDARD_REQUIRED ON)
# Determine command line style
if (DEFINED compiler_cl_dialect)
# Do nothing
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(compiler_cl_dialect "GCC")
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
set(compiler_cl_dialect "MSVC")
elseif (CMAKE_CXX_SIMULATE_ID STREQUAL "GCC")
set(compiler_cl_dialect "GCC")
elseif (CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC")
set(compiler_cl_dialect "MSVC")
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# On Linux, clang does not have SIMULATE_ID
set(compiler_cl_dialect "GCC")
elseif(WIN32)
message(WARNING "Could not determine compiler command line dialect, guessing MSVC")
set(compiler_cl_dialect "MSVC")
else()
message(WARNING "Could not determine compiler command line dialect, guessing GCC")
set(compiler_cl_dialect "GCC")
endif()
set(VERSION "0.0.1")
# Do Windows-specific tweaks for release builds
if (WIN32 AND NOT BUILD_ID STREQUAL "dev")
set_target_properties(progressia PROPERTIES WIN32_EXECUTABLE true)
# Debug options
option(VULKAN_ERROR_CHECKING "Enable Vulkan validation layers to detect Vulkan API usage errors at runtime")
if (compiler_cl_dialect STREQUAL "GCC")
target_link_options(progressia PRIVATE -static)
elseif (compiler_cl_dialect STREQUAL "MSVC")
target_link_options(progressia PRIVATE /entry:mainCRTStartup)
# The static linking options for standard libraries are not available for MSVC when using a GPLv3 license,
# as statically linking the standard C/C++ libraries would be a violation of the GPLv3 license.
# The GPL requires that any derivative work that includes GPL-licensed code must also be licensed under the GPL,
# and that the source code for the derivative work must be made available to anyone who receives the binary form.
# Statically linking the standard libraries with a GPLv3 license would create a derivative work,
# and would therefore require the entire program to be distributed under the terms of the GPL as well.
# To comply with the GPL, it is recommended to use shared library linking instead of static linking.
#
# Yours faithfully,
# ChatGPT
message(WARNING "Release builds with MSVC/Clang-CL are not supported")
endif()
endif()
# Pass configuration options
file(MAKE_DIRECTORY "${generated}/config")
configure_file(${PROJECT_SOURCE_DIR}/main/config.h.in
${PROJECT_BINARY_DIR}/config.h)
${generated}/config/config.h)
target_include_directories(progressia PRIVATE ${generated}/config)
# Libraries
@ -72,7 +143,3 @@ target_link_libraries(progressia glm::glm)
# Use STB
target_include_directories(progressia PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/lib/stb/include)
# Use Boost
find_package(Boost 1.74 REQUIRED)
target_link_libraries(progressia Boost::headers)

View File

@ -31,4 +31,3 @@ for details or help.
- [STB (GitHub)](https://github.com/nothings/stb) collection of various
algorithms
- `stb_image` PNG loading
- [Boost](https://www.boost.org/) utility library

View File

@ -8,59 +8,95 @@
#include "../../main/logging.h"
#include "../../main/meta.h"
#include "vulkan_mgmt.h"
#include "../../main/util.h"
using namespace progressia::main::logging;
namespace progressia {
namespace desktop {
static GLFWwindow *window = nullptr;
namespace progressia::desktop {
static void onGlfwError(int errorCode, const char *description);
static void onWindowGeometryChange(GLFWwindow *window, int width, int height);
void initializeGlfw() {
debug("Beginning GLFW init");
class GlfwManagerImpl : public GlfwManager {
private:
GLFWwindow *window = nullptr;
std::function<void()> onScreenResize = nullptr;
glfwSetErrorCallback(onGlfwError);
public:
DISABLE_COPYING(GlfwManagerImpl)
DISABLE_MOVING(GlfwManagerImpl)
if (!glfwInit()) {
fatal("glfwInit() failed");
GlfwManagerImpl() {
debug("Beginning GLFW init");
glfwSetErrorCallback(onGlfwError);
if (!glfwInit()) {
fatal("glfwInit() failed");
// REPORT_ERROR
exit(1);
}
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
std::string title;
{
std::stringstream accumulator;
accumulator << progressia::main::meta::NAME << " "
<< progressia::main::meta::VERSION << " build "
<< progressia::main::meta::BUILD_ID;
title = accumulator.str();
}
constexpr auto windowDimensions = 800;
window = glfwCreateWindow(windowDimensions, windowDimensions,
title.c_str(), nullptr, nullptr);
glfwSetWindowSizeCallback(window, onWindowGeometryChange);
debug("GLFW init complete");
}
~GlfwManagerImpl() override { glfwTerminate(); }
void setOnScreenResize(std::function<void()> hook) override {
onScreenResize = hook;
}
void showWindow() override {
glfwShowWindow(window);
debug("Window now visible");
}
bool shouldRun() override { return !glfwWindowShouldClose(window); }
void doGlfwRoutine() override { glfwPollEvents(); }
friend GLFWwindow *getGLFWWindowHandle();
friend void onWindowGeometryChange(GLFWwindow *, int, int);
};
namespace {
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables): global variables required by GLFW C callbacks
std::weak_ptr<GlfwManagerImpl> theGlfwManager;
} // namespace
std::shared_ptr<GlfwManager> makeGlfwManager() {
if (!theGlfwManager.expired()) {
fatal("GlfwManager already exists");
// REPORT_ERROR
exit(1);
}
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
std::shared_ptr<GlfwManagerImpl> aGlfwManager =
std::make_shared<GlfwManagerImpl>();
std::string title;
{
std::stringstream accumulator;
accumulator << progressia::main::meta::NAME << " "
<< progressia::main::meta::VERSION << " build "
<< progressia::main::meta::BUILD_ID;
title = accumulator.str();
}
window = glfwCreateWindow(800, 800, title.c_str(), nullptr, nullptr);
glfwSetWindowSizeCallback(window, onWindowGeometryChange);
debug("GLFW init complete");
theGlfwManager = aGlfwManager;
return aGlfwManager;
}
void showWindow() {
glfwShowWindow(window);
debug("Window now visible");
}
bool shouldRun() { return !glfwWindowShouldClose(window); }
void doGlfwRoutine() { glfwPollEvents(); }
void shutdownGlfw() { glfwTerminate(); }
void onGlfwError(int errorCode, const char *description) {
fatal() << "[GLFW] " << description << " (" << errorCode << ")";
// REPORT_ERROR
@ -69,14 +105,25 @@ void onGlfwError(int errorCode, const char *description) {
void onWindowGeometryChange(GLFWwindow *window, [[maybe_unused]] int width,
[[maybe_unused]] int height) {
if (window != progressia::desktop::window) {
if (auto manager = theGlfwManager.lock()) {
if (manager->window != window) {
return;
}
if (manager->onScreenResize != nullptr) {
manager->onScreenResize();
}
} else {
return;
}
resizeVulkanSurface();
}
GLFWwindow *getGLFWWindowHandle() { return window; }
GLFWwindow *getGLFWWindowHandle() {
if (auto manager = theGlfwManager.lock()) {
return manager->window;
}
} // namespace desktop
} // namespace progressia
return nullptr;
}
} // namespace progressia::desktop

View File

@ -1,13 +1,22 @@
#pragma once
namespace progressia {
namespace desktop {
#include <functional>
#include <memory>
void initializeGlfw();
void showWindow();
void shutdownGlfw();
bool shouldRun();
void doGlfwRoutine();
namespace progressia::desktop {
} // namespace desktop
} // namespace progressia
class GlfwManager {
public:
virtual ~GlfwManager(){};
virtual void setOnScreenResize(std::function<void()>) = 0;
virtual void showWindow() = 0;
virtual bool shouldRun() = 0;
virtual void doGlfwRoutine() = 0;
};
std::shared_ptr<GlfwManager> makeGlfwManager();
} // namespace progressia::desktop

View File

@ -5,10 +5,9 @@
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
namespace progressia {
namespace desktop {
namespace progressia::desktop {
// TODO refactor into OOP
GLFWwindow *getGLFWWindowHandle();
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop

View File

@ -2,6 +2,7 @@
#include "vulkan_common.h"
#include <array>
#include <cstddef>
#include <fstream>
#include <memory>
@ -25,8 +26,7 @@
#include <embedded_resources.h>
namespace progressia {
namespace desktop {
namespace progressia::desktop {
using progressia::main::Vertex;
@ -50,7 +50,7 @@ auto getVertexFieldProperties() {
namespace {
std::vector<char> tmp_readFile(const std::string &path) {
auto resource = __embedded_resources::getEmbeddedResource(path.c_str());
auto resource = __embedded_resources::getEmbeddedResource(path);
if (resource.data == nullptr) {
// REPORT_ERROR
@ -59,7 +59,7 @@ std::vector<char> tmp_readFile(const std::string &path) {
exit(1);
}
return std::vector<char>(resource.data, resource.data + resource.length);
return {resource.data, resource.data + resource.length};
}
} // namespace
@ -82,25 +82,26 @@ Adapter::Adapter(Vulkan &vulkan)
VK_ATTACHMENT_LOAD_OP_CLEAR,
VK_ATTACHMENT_STORE_OP_DONT_CARE,
{1.0f, 0},
{1.0F, 0},
nullptr});
}
Adapter::~Adapter() {
// Do nothing
}
Adapter::~Adapter() = default;
std::vector<Attachment> &Adapter::getAttachments() { return attachments; }
// NOLINTNEXTLINE(readability-convert-member-functions-to-static): future-proofing
std::vector<char> Adapter::loadVertexShader() {
return tmp_readFile("shader.vert.spv");
}
// NOLINTNEXTLINE(readability-convert-member-functions-to-static): future-proofing
std::vector<char> Adapter::loadFragmentShader() {
return tmp_readFile("shader.frag.spv");
}
// NOLINTNEXTLINE(readability-convert-member-functions-to-static): future-proofing
VkVertexInputBindingDescription Adapter::getVertexInputBindingDescription() {
VkVertexInputBindingDescription bindingDescription{};
bindingDescription.binding = 0;
@ -111,6 +112,7 @@ VkVertexInputBindingDescription Adapter::getVertexInputBindingDescription() {
}
std::vector<VkVertexInputAttributeDescription>
// NOLINTNEXTLINE(readability-convert-member-functions-to-static): future-proofing
Adapter::getVertexInputAttributeDescriptions() {
std::vector<VkVertexInputAttributeDescription> attributeDescriptions;
@ -151,8 +153,8 @@ void Adapter::onPreFrame() {
* graphics_interface implementation
*/
} // namespace desktop
namespace main {
} // namespace progressia::desktop
namespace progressia::main {
using namespace progressia::desktop;
@ -163,122 +165,125 @@ struct DrawRequest {
glm::mat4 modelTransform;
};
// NOLINTNEXTLINE: TODO
std::vector<DrawRequest> pendingDrawCommands;
constexpr std::size_t PENDING_DRAW_COMMANDS_MAX_SIZE = 100000;
// NOLINTNEXTLINE: TODO
glm::mat4 currentModelTransform;
} // namespace
progressia::main::Texture::Texture(Backend backend) : backend(backend) {}
struct progressia::main::Texture::Backend {
progressia::desktop::Texture texture;
};
progressia::main::Texture::~Texture() {
delete static_cast<progressia::desktop::Texture *>(this->backend);
}
progressia::main::Texture::Texture(std::unique_ptr<Backend> backend)
: backend(std::move(backend)) {}
namespace {
struct PrimitiveBackend {
progressia::main::Texture::~Texture() = default;
struct Primitive::Backend {
IndexedBuffer<Vertex> buf;
progressia::main::Texture *tex;
};
} // namespace
Primitive::Primitive(Backend backend) : backend(backend) {}
Primitive::Primitive(std::unique_ptr<Backend> backend)
: backend(std::move(backend)) {}
Primitive::~Primitive() {
delete static_cast<PrimitiveBackend *>(this->backend);
}
Primitive::~Primitive() = default;
void Primitive::draw() {
auto backend = static_cast<PrimitiveBackend *>(this->backend);
if (pendingDrawCommands.size() > 100000) {
if (pendingDrawCommands.size() > PENDING_DRAW_COMMANDS_MAX_SIZE) {
backend->buf.getVulkan().getGint().flush();
}
pendingDrawCommands.push_back(
{static_cast<progressia::desktop::Texture *>(backend->tex->backend),
&backend->buf, currentModelTransform});
pendingDrawCommands.push_back({&backend->tex->backend->texture,
&backend->buf, currentModelTransform});
}
const progressia::main::Texture *Primitive::getTexture() const {
return static_cast<PrimitiveBackend *>(this->backend)->tex;
return backend->tex;
}
View::View(Backend backend) : backend(backend) {}
struct View::Backend {
Adapter::ViewUniform::State state;
};
View::~View() {
delete static_cast<Adapter::ViewUniform::State *>(this->backend);
}
View::View(std::unique_ptr<Backend> backend) : backend(std::move(backend)) {}
View::~View() = default;
void View::configure(const glm::mat4 &proj, const glm::mat4 &view) {
static_cast<Adapter::ViewUniform::State *>(this->backend)
->update(proj, view);
backend->state.update(proj, view);
}
void View::use() {
auto backend = static_cast<Adapter::ViewUniform::State *>(this->backend);
backend->uniform->getVulkan().getGint().flush();
backend->bind();
backend->state.uniform->getVulkan().getGint().flush();
backend->state.bind();
}
Light::Light(Backend backend) : backend(backend) {}
struct Light::Backend {
Adapter::LightUniform::State state;
};
Light::~Light() {
delete static_cast<Adapter::LightUniform::State *>(this->backend);
}
Light::Light(std::unique_ptr<Backend> backend) : backend(std::move(backend)) {}
Light::~Light() = default;
void Light::configure(const glm::vec3 &color, const glm::vec3 &from,
float contrast, float softness) {
static_cast<Adapter::LightUniform::State *>(this->backend)
->update(Adapter::Light{glm::vec4(color, 1.0f),
glm::vec4(glm::normalize(from), 1.0f), contrast,
softness});
backend->state.update(Adapter::Light{glm::vec4(color, 1.0F),
glm::vec4(glm::normalize(from), 1.0F),
contrast, softness});
}
void Light::use() {
auto backend = static_cast<Adapter::LightUniform::State *>(this->backend);
backend->uniform->getVulkan().getGint().flush();
backend->bind();
backend->state.uniform->getVulkan().getGint().flush();
backend->state.bind();
}
GraphicsInterface::GraphicsInterface(Backend backend) : backend(backend) {}
GraphicsInterface::~GraphicsInterface() {
// Do nothing
}
GraphicsInterface::~GraphicsInterface() = default;
progressia::main::Texture *
std::unique_ptr<progressia::main::Texture>
GraphicsInterface::newTexture(const progressia::main::Image &src) {
auto backend = new progressia::desktop::Texture(
src, *static_cast<Vulkan *>(this->backend));
using Backend = progressia::main::Texture::Backend;
return new Texture(backend);
return std::make_unique<progressia::main::Texture>(
std::unique_ptr<Backend>(new Backend{progressia::desktop::Texture(
src, *static_cast<Vulkan *>(this->backend))}));
}
Primitive *
std::unique_ptr<Primitive>
GraphicsInterface::newPrimitive(const std::vector<Vertex> &vertices,
const std::vector<Vertex::Index> &indices,
progressia::main::Texture *texture) {
auto backend = new PrimitiveBackend{
IndexedBuffer<Vertex>(vertices.size(), indices.size(),
*static_cast<Vulkan *>(this->backend)),
texture};
auto primitive = std::make_unique<Primitive>(
std::unique_ptr<Primitive::Backend>(new Primitive::Backend{
IndexedBuffer<Vertex>(vertices.size(), indices.size(),
*static_cast<Vulkan *>(this->backend)),
texture}));
backend->buf.load(vertices.data(), indices.data());
primitive->backend->buf.load(vertices.data(), indices.data());
return new Primitive(backend);
return primitive;
}
View *GraphicsInterface::newView() {
return new View(new Adapter::ViewUniform::State(
static_cast<Vulkan *>(this->backend)->getAdapter().createView()));
std::unique_ptr<View> GraphicsInterface::newView() {
return std::make_unique<View>(std::unique_ptr<View::Backend>(
new View::Backend{Adapter::ViewUniform::State(
static_cast<Vulkan *>(this->backend)->getAdapter().createView())}));
}
Light *GraphicsInterface::newLight() {
return new Light(new Adapter::LightUniform::State(
static_cast<Vulkan *>(this->backend)->getAdapter().createLight()));
std::unique_ptr<Light> GraphicsInterface::newLight() {
return std::make_unique<Light>(
std::unique_ptr<Light::Backend>(new Light::Backend{
Adapter::LightUniform::State(static_cast<Vulkan *>(this->backend)
->getAdapter()
.createLight())}));
}
glm::vec2 GraphicsInterface::getViewport() const {
@ -287,16 +292,17 @@ glm::vec2 GraphicsInterface::getViewport() const {
return {extent.width, extent.height};
}
// NOLINTNEXTLINE(readability-convert-member-functions-to-static): future-proofing
void GraphicsInterface::setModelTransform(const glm::mat4 &m) {
currentModelTransform = m;
}
void GraphicsInterface::flush() {
auto commandBuffer = static_cast<Vulkan *>(this->backend)
->getCurrentFrame()
->getCommandBuffer();
auto pipelineLayout =
auto *commandBuffer = static_cast<Vulkan *>(this->backend)
->getCurrentFrame()
->getCommandBuffer();
auto *pipelineLayout =
static_cast<Vulkan *>(this->backend)->getPipeline().getLayout();
progressia::desktop::Texture *lastTexture = nullptr;
@ -328,11 +334,11 @@ void GraphicsInterface::flush() {
pendingDrawCommands.clear();
}
// NOLINTNEXTLINE: TODO
float GraphicsInterface::tmp_getTime() { return glfwGetTime(); }
uint64_t GraphicsInterface::getLastStartedFrame() {
return static_cast<Vulkan *>(this->backend)->getLastStartedFrame();
}
} // namespace main
} // namespace progressia
} // namespace progressia::main

View File

@ -1,13 +1,11 @@
#pragma once
#include "boost/core/noncopyable.hpp"
#include "vulkan_common.h"
#include "vulkan_descriptor_set.h"
#include "vulkan_image.h"
#include "vulkan_uniform.h"
namespace progressia {
namespace desktop {
namespace progressia::desktop {
class Attachment {
public:
@ -68,5 +66,4 @@ class Adapter : public VkObjectWrapper {
void onPreFrame();
};
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop

View File

@ -1,12 +1,10 @@
#pragma once
#include <boost/core/noncopyable.hpp>
#include <vector>
#include "vulkan_common.h"
namespace progressia {
namespace desktop {
namespace progressia::desktop {
/*
* A single buffer with a chunk of allocated memory.
@ -192,5 +190,4 @@ class IndexedBufferBase : public VkObjectWrapper {
template <typename Vertex>
using IndexedBuffer = IndexedBufferBase<Vertex, uint16_t, VK_INDEX_TYPE_UINT16>;
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop

View File

@ -1,8 +1,8 @@
#include "vulkan_common.h"
#include "../config.h"
#include "vulkan_adapter.h"
#include "vulkan_frame.h"
#include "vulkan_physical_device.h"
#include "vulkan_pick_device.h"
#include "vulkan_pipeline.h"
#include "vulkan_render_pass.h"
@ -15,8 +15,7 @@
using namespace progressia::main::logging;
namespace progressia {
namespace desktop {
namespace progressia::desktop {
/*
* Vulkan
@ -27,7 +26,7 @@ Vulkan::Vulkan(std::vector<const char *> instanceExtensions,
std::vector<const char *> validationLayers)
:
frames(MAX_FRAMES_IN_FLIGHT), isRenderingFrame(false),
frames(MAX_FRAMES_IN_FLIGHT), currentFrame(0), isRenderingFrame(false),
lastStartedFrame(0) {
/*
@ -58,7 +57,7 @@ Vulkan::Vulkan(std::vector<const char *> instanceExtensions,
// Enable extensions
{
uint32_t extensionCount;
uint32_t extensionCount = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount,
nullptr);
std::vector<VkExtensionProperties> available(extensionCount);
@ -89,7 +88,7 @@ Vulkan::Vulkan(std::vector<const char *> instanceExtensions,
// Enable validation layers
{
uint32_t layerCount;
uint32_t layerCount = 0;
vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
std::vector<VkLayerProperties> available(layerCount);
vkEnumerateInstanceLayerProperties(&layerCount, available.data());
@ -150,31 +149,25 @@ Vulkan::Vulkan(std::vector<const char *> instanceExtensions,
exit(1);
}
std::vector<VkPhysicalDevice> devices(deviceCount);
vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
std::vector<VkPhysicalDevice> vkDevices(deviceCount);
vkEnumeratePhysicalDevices(instance, &deviceCount, vkDevices.data());
std::vector<PhysicalDeviceData> choices;
for (const auto &device : devices) {
PhysicalDeviceData data = {};
data.device = device;
vkGetPhysicalDeviceProperties(device, &data.properties);
vkGetPhysicalDeviceFeatures(device, &data.features);
choices.push_back(data);
std::vector<PhysicalDevice> choices;
choices.reserve(deviceCount);
for (const auto &vkDevice : vkDevices) {
choices.emplace_back(PhysicalDevice(vkDevice));
}
const auto &result =
pickPhysicalDevice(choices, *this, deviceExtensions);
physicalDevice = result.device;
physicalDevice = std::make_unique<PhysicalDevice>(result);
}
/*
* Setup queues
*/
queues = std::make_unique<Queues>(physicalDevice, *this);
queues = std::make_unique<Queues>(physicalDevice->getVk(), *this);
/*
* Create logical device
@ -207,9 +200,9 @@ Vulkan::Vulkan(std::vector<const char *> instanceExtensions,
// Create logical device
handleVkResult(
"Could not create logical device",
vkCreateDevice(physicalDevice, &createInfo, nullptr, &device));
handleVkResult("Could not create logical device",
vkCreateDevice(physicalDevice->getVk(), &createInfo,
nullptr, &device));
// Store queue handles
@ -259,7 +252,6 @@ Vulkan::Vulkan(std::vector<const char *> instanceExtensions,
for (auto &container : frames) {
container.emplace(*this);
}
currentFrame = 0;
gint = std::make_unique<progressia::main::GraphicsInterface>(this);
}
@ -275,13 +267,16 @@ Vulkan::~Vulkan() {
commandPool.reset();
vkDestroyDevice(device, nullptr);
surface.reset();
physicalDevice.reset();
errorHandler.reset();
vkDestroyInstance(instance, nullptr);
}
VkInstance Vulkan::getInstance() const { return instance; }
VkPhysicalDevice Vulkan::getPhysicalDevice() const { return physicalDevice; }
const PhysicalDevice &Vulkan::getPhysicalDevice() const {
return *physicalDevice;
}
VkDevice Vulkan::getDevice() const { return device; }
@ -333,7 +328,8 @@ VkFormat Vulkan::findSupportedFormat(const std::vector<VkFormat> &candidates,
for (VkFormat format : candidates) {
VkFormatProperties props;
vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props);
vkGetPhysicalDeviceFormatProperties(physicalDevice->getVk(), format,
&props);
if (tiling == VK_IMAGE_TILING_LINEAR &&
(props.linearTilingFeatures & features) == features) {
@ -351,8 +347,7 @@ VkFormat Vulkan::findSupportedFormat(const std::vector<VkFormat> &candidates,
uint32_t Vulkan::findMemoryType(uint32_t allowedByDevice,
VkMemoryPropertyFlags desiredProperties) {
VkPhysicalDeviceMemoryProperties memProperties;
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
auto memProperties = physicalDevice->getMemory();
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
if (((1 << i) & allowedByDevice) == 0) {
@ -383,9 +378,9 @@ Frame *Vulkan::getCurrentFrame() {
return nullptr;
}
uint64_t Vulkan::getLastStartedFrame() { return lastStartedFrame; }
uint64_t Vulkan::getLastStartedFrame() const { return lastStartedFrame; }
std::size_t Vulkan::getFrameInFlightIndex() { return currentFrame; }
std::size_t Vulkan::getFrameInFlightIndex() const { return currentFrame; }
bool Vulkan::startRender() {
if (currentFrame >= MAX_FRAMES_IN_FLIGHT - 1) {
@ -421,16 +416,21 @@ void Vulkan::waitIdle() {
* VulkanErrorHandler
*/
VulkanErrorHandler::VulkanErrorHandler(Vulkan &vulkan) : vulkan(vulkan) {
VulkanErrorHandler::VulkanErrorHandler(Vulkan &vulkan)
: debugMessenger(nullptr), vulkan(vulkan) {
// do nothing
}
VulkanErrorHandler::~VulkanErrorHandler() {
#ifdef VULKAN_ERROR_CHECKING
vulkan.callVoid("vkDestroyDebugUtilsMessengerEXT",
(VkDebugUtilsMessengerEXT)debugMessenger, nullptr);
#endif
VulkanErrorHandler::~VulkanErrorHandler() {
if (debugMessenger != nullptr) {
vulkan.callVoid("vkDestroyDebugUtilsMessengerEXT",
(VkDebugUtilsMessengerEXT)debugMessenger, nullptr);
}
}
#else
VulkanErrorHandler::~VulkanErrorHandler() = default;
#endif
#ifdef VULKAN_ERROR_CHECKING
namespace {
@ -445,7 +445,8 @@ debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
return VK_FALSE;
}
[[maybe_unused]] auto &vk = *reinterpret_cast<const Vulkan *>(pUserData);
[[maybe_unused]] const auto &vk =
*reinterpret_cast<const Vulkan *>(pUserData);
const char *severityStr =
messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT
@ -456,7 +457,7 @@ debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
? "info"
: "verbose";
const char *typeStr;
const char *typeStr = "";
switch (messageType) {
case VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT:
typeStr = "general";
@ -513,8 +514,9 @@ VulkanErrorHandler::attachDebugProbe(VkInstanceCreateInfo &createInfo) {
#else
(void)createInfo;
return std::unique_ptr<VkDebugUtilsMessengerCreateInfoEXT>();
(void)createInfo; // unused argument
(void)this; // not static
return {};
#endif
}
@ -533,6 +535,7 @@ void VulkanErrorHandler::onInstanceReady() {
#endif
}
// NOLINTNEXTLINE(readability-convert-member-functions-to-static): future-proofing
void VulkanErrorHandler::handleVkResult(const char *errorMessage,
VkResult result) {
if (result == VK_SUCCESS) {
@ -548,7 +551,7 @@ void VulkanErrorHandler::handleVkResult(const char *errorMessage,
* Surface
*/
Surface::Surface(Vulkan &vulkan) : vulkan(vulkan) {
Surface::Surface(Vulkan &vulkan) : vk(), vulkan(vulkan) {
vulkan.handleVkResult("Could not create window surface (what?)",
glfwCreateWindowSurface(vulkan.getInstance(),
getGLFWWindowHandle(),
@ -563,7 +566,7 @@ VkSurfaceKHR Surface::getVk() { return vk; }
* Queue
*/
Queue::Queue(Test test) : test(test) {
Queue::Queue(Test test) : test(std::move(test)), vk() {
// do nothing
}
@ -619,7 +622,7 @@ Queues::Queues(VkPhysicalDevice physicalDevice, Vulkan &vulkan)
for (std::size_t index = 0; index < queueFamilyCount; index++) {
for (auto queue : {&graphicsQueue, &presentQueue}) {
for (auto *queue : {&graphicsQueue, &presentQueue}) {
if (!queue->isSuitable(physicalDevice, index, vulkan,
properties[index])) {
continue;
@ -634,12 +637,10 @@ Queues::Queues(VkPhysicalDevice physicalDevice, Vulkan &vulkan)
}
}
Queues::~Queues() {
// do nothing
}
Queues::~Queues() = default;
void Queues::storeHandles(VkDevice device) {
for (auto queue : {&graphicsQueue, &presentQueue}) {
for (auto *queue : {&graphicsQueue, &presentQueue}) {
vkGetDeviceQueue(device, queue->getFamilyIndex(), 0, &queue->vk);
}
}
@ -648,7 +649,7 @@ std::unique_ptr<Queues::CreationRequest>
Queues::requestCreation(VkDeviceCreateInfo &createInfo) const {
std::unique_ptr result = std::make_unique<CreationRequest>();
result->priority = 1.0f;
result->priority = 1.0F;
std::unordered_set<uint32_t> uniqueQueues;
for (const auto *queue : {&graphicsQueue, &presentQueue}) {
@ -673,7 +674,7 @@ Queues::requestCreation(VkDeviceCreateInfo &createInfo) const {
}
bool Queues::isComplete() const {
for (auto queue : {&graphicsQueue, &presentQueue}) {
for (const auto *queue : {&graphicsQueue, &presentQueue}) {
if (!queue->familyIndex.has_value()) {
return false;
}
@ -691,7 +692,7 @@ const Queue &Queues::getPresentQueue() const { return presentQueue; }
*/
CommandPool::CommandPool(Vulkan &vulkan, const Queue &queue)
: queue(queue), vulkan(vulkan) {
: pool(), queue(queue), vulkan(vulkan) {
VkCommandPoolCreateInfo poolInfo{};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
@ -714,12 +715,13 @@ VkCommandBuffer CommandPool::allocateCommandBuffer() {
allocInfo.commandPool = pool;
allocInfo.commandBufferCount = 1;
VkCommandBuffer commandBuffer;
auto *commandBuffer = VkCommandBuffer();
vkAllocateCommandBuffers(vulkan.getDevice(), &allocInfo, &commandBuffer);
return commandBuffer;
}
// NOLINTNEXTLINE(readability-convert-member-functions-to-static): future-proofing
void CommandPool::beginCommandBuffer(VkCommandBuffer commandBuffer,
VkCommandBufferUsageFlags usage) {
VkCommandBufferBeginInfo beginInfo{};
@ -773,5 +775,4 @@ void CommandPool::freeMultiUse(VkCommandBuffer buffer) {
vkFreeCommandBuffers(vulkan.getDevice(), pool, 1, &buffer);
}
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop

View File

@ -17,13 +17,12 @@
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
#include <boost/core/noncopyable.hpp>
#include "../../main/util.h"
#include "../../main/logging.h"
#include "../../main/rendering/graphics_interface.h"
namespace progressia {
namespace desktop {
namespace progressia::desktop {
namespace CstrUtils {
struct CstrHash {
@ -54,13 +53,14 @@ struct CstrCompare {
using CstrHashSet = std::unordered_set<const char *, CstrHash, CstrEqual>;
} // namespace CstrUtils
class VkObjectWrapper : private boost::noncopyable {
class VkObjectWrapper : private progressia::main::NonCopyable {
// empty
};
constexpr std::size_t MAX_FRAMES_IN_FLIGHT = 2;
class VulkanErrorHandler;
class PhysicalDevice;
class Surface;
class Queue;
class Queues;
@ -75,10 +75,10 @@ class Frame;
class Vulkan : public VkObjectWrapper {
private:
VkInstance instance = VK_NULL_HANDLE;
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
VkDevice device = VK_NULL_HANDLE;
std::unique_ptr<VulkanErrorHandler> errorHandler;
std::unique_ptr<PhysicalDevice> physicalDevice;
std::unique_ptr<Surface> surface;
std::unique_ptr<Queues> queues;
std::unique_ptr<CommandPool> commandPool;
@ -103,9 +103,9 @@ class Vulkan : public VkObjectWrapper {
~Vulkan();
VkInstance getInstance() const;
VkPhysicalDevice getPhysicalDevice() const;
VkDevice getDevice() const;
const PhysicalDevice &getPhysicalDevice() const;
Surface &getSurface();
const Surface &getSurface() const;
Queues &getQueues();
@ -135,8 +135,8 @@ class Vulkan : public VkObjectWrapper {
bool startRender();
void endRender();
uint64_t getLastStartedFrame();
std::size_t getFrameInFlightIndex();
uint64_t getLastStartedFrame() const;
std::size_t getFrameInFlightIndex() const;
void waitIdle();
@ -192,12 +192,13 @@ class VulkanErrorHandler : public VkObjectWrapper {
Vulkan &vulkan;
public:
VulkanErrorHandler(Vulkan &);
VulkanErrorHandler(Vulkan &vulkan);
std::unique_ptr<VkDebugUtilsMessengerCreateInfoEXT>
attachDebugProbe(VkInstanceCreateInfo &);
void onInstanceReady();
// NOLINTNEXTLINE(performance-trivially-destructible): fixing this makes code less readable due to use of macros in implementation
~VulkanErrorHandler();
void handleVkResult(const char *errorMessage, VkResult result);
@ -209,7 +210,7 @@ class Surface : public VkObjectWrapper {
Vulkan &vulkan;
public:
Surface(Vulkan &);
Surface(Vulkan &vulkan);
~Surface();
VkSurfaceKHR getVk();
@ -226,7 +227,7 @@ class Queue {
friend class Queues;
Queue(Test);
Queue(Test test);
public:
bool isSuitable(VkPhysicalDevice, uint32_t familyIndex, Vulkan &,
@ -275,7 +276,7 @@ class CommandPool : public VkObjectWrapper {
VkCommandBufferUsageFlags usage);
public:
CommandPool(Vulkan &, const Queue &);
CommandPool(Vulkan &vulkan, const Queue &queue);
~CommandPool();
VkCommandBuffer beginSingleUse();
@ -287,5 +288,4 @@ class CommandPool : public VkObjectWrapper {
void freeMultiUse(VkCommandBuffer);
};
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop

View File

@ -1,11 +1,10 @@
#include "vulkan_descriptor_set.h"
namespace progressia {
namespace desktop {
namespace progressia::desktop {
DescriptorSetInterface::DescriptorSetInterface(uint32_t setNumber,
Vulkan &vulkan)
: setNumber(setNumber), vulkan(vulkan) {}
: layout(), setNumber(setNumber), vulkan(vulkan) {}
VkDescriptorSetLayout DescriptorSetInterface::getLayout() const {
return layout;
@ -15,5 +14,4 @@ uint32_t DescriptorSetInterface::getSetNumber() const { return setNumber; }
Vulkan &DescriptorSetInterface::getVulkan() { return vulkan; }
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop

View File

@ -2,8 +2,7 @@
#include "vulkan_common.h"
namespace progressia {
namespace desktop {
namespace progressia::desktop {
class DescriptorSetInterface : public VkObjectWrapper {
protected:
@ -19,5 +18,4 @@ class DescriptorSetInterface : public VkObjectWrapper {
Vulkan &getVulkan();
};
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop

View File

@ -8,12 +8,11 @@
#include "vulkan_render_pass.h"
#include "vulkan_swap_chain.h"
namespace progressia {
namespace desktop {
namespace progressia::desktop {
Frame::Frame(Vulkan &vulkan)
: vulkan(vulkan),
commandBuffer(vulkan.getCommandPool().allocateMultiUse()) {
: vulkan(vulkan), commandBuffer(vulkan.getCommandPool().allocateMultiUse()),
imageAvailableSemaphore(), renderFinishedSemaphore(), inFlightFence() {
VkSemaphoreCreateInfo semaphoreInfo{};
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
@ -98,12 +97,12 @@ bool Frame::startRender() {
vulkan.getPipeline().getVk());
VkViewport viewport{};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.x = 0.0F;
viewport.y = 0.0F;
viewport.width = (float)extent.width;
viewport.height = (float)extent.height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
viewport.minDepth = 0.0F;
viewport.maxDepth = 1.0F;
vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
VkRect2D scissor{};
@ -170,5 +169,4 @@ void Frame::endRender() {
VkCommandBuffer Frame::getCommandBuffer() { return commandBuffer; }
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop

View File

@ -2,8 +2,7 @@
#include "vulkan_common.h"
namespace progressia {
namespace desktop {
namespace progressia::desktop {
class Frame : public VkObjectWrapper {
private:
@ -32,5 +31,4 @@ class Frame : public VkObjectWrapper {
VkCommandBuffer getCommandBuffer();
};
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop

View File

@ -9,8 +9,7 @@
#include "vulkan_pipeline.h"
#include "vulkan_texture_descriptors.h"
namespace progressia {
namespace desktop {
namespace progressia::desktop {
/*
* Image
@ -21,9 +20,7 @@ Image::Image(VkImage vk, VkImageView view, VkFormat format)
// do nothing
}
Image::~Image() {
// do nothing
}
Image::~Image() = default;
/*
* ManagedImage
@ -34,7 +31,7 @@ ManagedImage::ManagedImage(std::size_t width, std::size_t height,
VkImageUsageFlags usage, Vulkan &vulkan)
:
Image(VK_NULL_HANDLE, VK_NULL_HANDLE, format), vulkan(vulkan),
Image(VK_NULL_HANDLE, VK_NULL_HANDLE, format), memory(), vulkan(vulkan),
state{VK_IMAGE_LAYOUT_UNDEFINED, 0, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT} {
@ -147,7 +144,8 @@ Texture::Texture(const progressia::main::Image &src, Vulkan &vulkan)
ManagedImage(src.width, src.height, VK_FORMAT_R8G8B8A8_SRGB,
VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
vulkan) {
vulkan),
sampler() {
/*
* Create a staging buffer
@ -212,9 +210,9 @@ Texture::Texture(const progressia::main::Image &src, Vulkan &vulkan)
samplerInfo.compareEnable = VK_FALSE;
samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
samplerInfo.mipLodBias = 0.0f;
samplerInfo.minLod = 0.0f;
samplerInfo.maxLod = 0.0f;
samplerInfo.mipLodBias = 0.0F;
samplerInfo.minLod = 0.0F;
samplerInfo.maxLod = 0.0F;
vulkan.handleVkResult(
"Could not create texture sampler",
@ -224,6 +222,7 @@ Texture::Texture(const progressia::main::Image &src, Vulkan &vulkan)
* Create descriptor set
*/
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer): sampler must be set using vkCreateSampler first
descriptorSet = vulkan.getTextureDescriptors().addTexture(view, sampler);
}
@ -234,8 +233,8 @@ Texture::~Texture() {
void Texture::bind() {
// REPORT_ERROR if getCurrentFrame() == nullptr
auto commandBuffer = vulkan.getCurrentFrame()->getCommandBuffer();
auto pipelineLayout = vulkan.getPipeline().getLayout();
auto *commandBuffer = vulkan.getCurrentFrame()->getCommandBuffer();
auto *pipelineLayout = vulkan.getPipeline().getLayout();
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
pipelineLayout,
@ -243,5 +242,4 @@ void Texture::bind() {
&descriptorSet, 0, nullptr);
}
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop

View File

@ -1,6 +1,5 @@
#pragma once
#include <boost/core/noncopyable.hpp>
#include <vector>
#include "vulkan_buffer.h"
@ -8,8 +7,7 @@
#include "../../main/rendering/image.h"
namespace progressia {
namespace desktop {
namespace progressia::desktop {
class Image : public VkObjectWrapper {
public:
@ -37,8 +35,9 @@ class ManagedImage : public Image {
State state;
public:
ManagedImage(std::size_t width, std::size_t height, VkFormat,
VkImageAspectFlags, VkImageUsageFlags, Vulkan &);
ManagedImage(std::size_t width, std::size_t height, VkFormat format,
VkImageAspectFlags aspect, VkImageUsageFlags usage,
Vulkan &vulkan);
~ManagedImage();
void transition(State);
@ -50,11 +49,10 @@ class Texture : public ManagedImage {
VkSampler sampler;
VkDescriptorSet descriptorSet;
Texture(const progressia::main::Image &, Vulkan &vulkan);
Texture(const main::Image &src, Vulkan &vulkan);
~Texture();
void bind();
};
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop

View File

@ -1,34 +1,32 @@
#include "vulkan_mgmt.h"
#include "../config.h"
#include "vulkan_common.h"
#include "vulkan_swap_chain.h"
#include "../../main/logging.h"
using namespace progressia::main::logging;
namespace progressia {
namespace desktop {
namespace progressia::desktop {
Vulkan *vulkan;
void initializeVulkan() {
VulkanManager::VulkanManager() {
debug("Vulkan initializing");
// Instance extensions
std::vector<const char *> instanceExtensions;
{
uint32_t glfwExtensionCount;
const char **glfwExtensions;
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
uint32_t glfwExtensionCount = 0;
const char **glfwExtensions =
glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
instanceExtensions.reserve(instanceExtensions.size() +
glfwExtensionCount);
for (std::size_t i = 0; i < glfwExtensionCount; i++) {
instanceExtensions.push_back(glfwExtensions[i]);
instanceExtensions.emplace_back(glfwExtensions[i]);
}
#ifdef VULKAN_ERROR_CHECKING
instanceExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
instanceExtensions.emplace_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
#endif
}
@ -44,29 +42,21 @@ void initializeVulkan() {
#endif
};
vulkan = new Vulkan(instanceExtensions, deviceExtensions, validationLayers);
vulkan = std::make_unique<Vulkan>(instanceExtensions, deviceExtensions,
validationLayers);
debug("Vulkan initialized");
}
Vulkan *getVulkan() { return vulkan; }
VulkanManager::~VulkanManager() { debug("Vulkan terminating"); }
bool startRender() { return vulkan->startRender(); }
Vulkan *VulkanManager::getVulkan() { return vulkan.get(); }
const Vulkan *VulkanManager::getVulkan() const { return vulkan.get(); }
void endRender() { return vulkan->endRender(); }
bool VulkanManager::startRender() { return vulkan->startRender(); }
void resizeVulkanSurface() { vulkan->getSwapChain().recreate(); }
void VulkanManager::endRender() { return vulkan->endRender(); }
void shutdownVulkan() {
debug("Vulkan terminating");
void VulkanManager::resizeSurface() { vulkan->getSwapChain().recreate(); }
if (vulkan != nullptr) {
delete vulkan;
vulkan = nullptr;
}
debug("Vulkan terminated");
}
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop

View File

@ -2,22 +2,27 @@
#include "vulkan_common.h"
namespace progressia {
namespace desktop {
namespace progressia::desktop {
void initializeVulkan();
class VulkanManager {
Vulkan *getVulkan();
private:
std::unique_ptr<Vulkan> vulkan;
void resizeVulkanSurface();
public:
VulkanManager();
~VulkanManager();
/*
* Returns false when the frame should be skipped
*/
bool startRender();
void endRender();
Vulkan *getVulkan();
const Vulkan *getVulkan() const;
void shutdownVulkan();
void resizeSurface();
} // namespace desktop
} // namespace progressia
/*
* Returns false when the frame should be skipped
*/
bool startRender();
void endRender();
};
} // namespace progressia::desktop

View File

@ -0,0 +1,51 @@
#include "vulkan_physical_device.h"
namespace progressia::desktop {
PhysicalDevice::PhysicalDevice(VkPhysicalDevice vk)
: vk(vk), properties(), features(), memory() {
vkGetPhysicalDeviceProperties(vk, &properties);
vkGetPhysicalDeviceFeatures(vk, &features);
vkGetPhysicalDeviceMemoryProperties(vk, &memory);
}
// NOLINTNEXTLINE(readability-convert-member-functions-to-static): future-proofing
bool PhysicalDevice::isSuitable() const {
// Add feature, limit, etc. checks here.
// Return false and debug() if problems arise.
return true;
}
VkPhysicalDevice PhysicalDevice::getVk() const { return vk; }
const VkPhysicalDeviceProperties &PhysicalDevice::getProperties() const {
return properties;
}
const VkPhysicalDeviceFeatures &PhysicalDevice::getFeatures() const {
return features;
}
const VkPhysicalDeviceLimits &PhysicalDevice::getLimits() const {
return properties.limits;
}
const VkPhysicalDeviceMemoryProperties &PhysicalDevice::getMemory() const {
return memory;
}
VkPhysicalDeviceType PhysicalDevice::getType() const {
return properties.deviceType;
}
const char *PhysicalDevice::getName() const { return properties.deviceName; }
VkDeviceSize PhysicalDevice::getMinUniformOffset() const {
return getLimits().minUniformBufferOffsetAlignment;
}
uint32_t PhysicalDevice::getMaxTextureSize() const {
return getLimits().maxImageDimension2D;
}
} // namespace progressia::desktop

View File

@ -0,0 +1,33 @@
#pragma once
#include "vulkan_common.h"
namespace progressia::desktop {
class PhysicalDevice {
private:
VkPhysicalDevice vk;
VkPhysicalDeviceProperties properties;
VkPhysicalDeviceFeatures features;
VkPhysicalDeviceMemoryProperties memory;
public:
PhysicalDevice(VkPhysicalDevice vk);
bool isSuitable() const;
VkPhysicalDevice getVk() const;
const VkPhysicalDeviceProperties &getProperties() const;
const VkPhysicalDeviceFeatures &getFeatures() const;
const VkPhysicalDeviceLimits &getLimits() const;
const VkPhysicalDeviceMemoryProperties &getMemory() const;
VkPhysicalDeviceType getType() const;
const char *getName() const;
VkDeviceSize getMinUniformOffset() const;
uint32_t getMaxTextureSize() const;
};
} // namespace progressia::desktop

View File

@ -4,8 +4,7 @@
#include "vulkan_swap_chain.h"
using namespace progressia::main::logging;
namespace progressia {
namespace desktop {
namespace progressia::desktop {
namespace {
@ -14,7 +13,7 @@ bool checkDeviceExtensions(VkPhysicalDevice device,
CstrUtils::CstrHashSet toFind(deviceExtensions.cbegin(),
deviceExtensions.cend());
uint32_t extensionCount;
uint32_t extensionCount = 0;
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount,
nullptr);
@ -29,20 +28,24 @@ bool checkDeviceExtensions(VkPhysicalDevice device,
return toFind.empty();
}
bool isDeviceSuitable(const PhysicalDeviceData &data, Vulkan &vulkan,
bool isDeviceSuitable(const PhysicalDevice &data, Vulkan &vulkan,
const std::vector<const char *> &deviceExtensions) {
if (!Queues(data.device, vulkan).isComplete()) {
if (!data.isSuitable()) {
return false;
}
if (!checkDeviceExtensions(data.device, deviceExtensions)) {
if (!Queues(data.getVk(), vulkan).isComplete()) {
return false;
}
if (!checkDeviceExtensions(data.getVk(), deviceExtensions)) {
return false;
}
// Check requires that the swap chain extension is present
if (!SwapChain::isSwapChainSuitable(
SwapChain::querySwapChainSupport(data.device, vulkan))) {
SwapChain::querySwapChainSupport(data.getVk(), vulkan))) {
return false;
}
@ -51,8 +54,8 @@ bool isDeviceSuitable(const PhysicalDeviceData &data, Vulkan &vulkan,
} // namespace
const PhysicalDeviceData &
pickPhysicalDevice(std::vector<PhysicalDeviceData> &choices, Vulkan &vulkan,
const PhysicalDevice &
pickPhysicalDevice(std::vector<PhysicalDevice> &choices, Vulkan &vulkan,
const std::vector<const char *> &deviceExtensions) {
// Remove unsuitable devices
@ -82,20 +85,17 @@ pickPhysicalDevice(std::vector<PhysicalDeviceData> &choices, Vulkan &vulkan,
{"Virtual GPU", +1},
{"CPU", -1}};
auto type = option.properties.deviceType;
m << "\n\t- " << opinions[type].description << " "
<< option.properties.deviceName;
auto type = option.getType();
m << "\n\t- " << opinions[type].description << " " << option.getName();
if (opinions[pick->properties.deviceType].value <
opinions[type].value) {
if (opinions[pick->getType()].value < opinions[type].value) {
pick = &option;
}
}
m << "\n";
m << "Picked device " << pick->properties.deviceName;
m << "Picked device " << pick->getName();
return *pick;
}
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop

View File

@ -1,21 +1,14 @@
#pragma once
#include "vulkan_common.h"
#include "vulkan_physical_device.h"
#include <vector>
namespace progressia {
namespace desktop {
namespace progressia::desktop {
struct PhysicalDeviceData {
VkPhysicalDevice device;
VkPhysicalDeviceProperties properties;
VkPhysicalDeviceFeatures features;
};
const PhysicalDeviceData &
pickPhysicalDevice(std::vector<PhysicalDeviceData> &, Vulkan &,
const PhysicalDevice &
pickPhysicalDevice(std::vector<PhysicalDevice> &, Vulkan &,
const std::vector<const char *> &deviceExtensions);
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop

View File

@ -5,17 +5,16 @@
#include "vulkan_descriptor_set.h"
#include "vulkan_render_pass.h"
namespace progressia {
namespace desktop {
namespace progressia::desktop {
Pipeline::Pipeline(Vulkan &vulkan) : vulkan(vulkan) {
Pipeline::Pipeline(Vulkan &vulkan) : layout(), vk(), vulkan(vulkan) {
auto &adapter = vulkan.getAdapter();
// Shaders
auto vertShader = createShaderModule(adapter.loadVertexShader());
auto fragShader = createShaderModule(adapter.loadFragmentShader());
auto *vertShader = createShaderModule(adapter.loadVertexShader());
auto *fragShader = createShaderModule(adapter.loadFragmentShader());
VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
vertShaderStageInfo.sType =
@ -81,13 +80,13 @@ Pipeline::Pipeline(Vulkan &vulkan) : vulkan(vulkan) {
rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
rasterizer.lineWidth = 1.0f;
rasterizer.lineWidth = 1.0F;
rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
rasterizer.depthBiasEnable = VK_FALSE;
rasterizer.depthBiasConstantFactor = 0.0f; // Optional
rasterizer.depthBiasClamp = 0.0f; // Optional
rasterizer.depthBiasSlopeFactor = 0.0f; // Optional
rasterizer.depthBiasConstantFactor = 0.0F; // Optional
rasterizer.depthBiasClamp = 0.0F; // Optional
rasterizer.depthBiasSlopeFactor = 0.0F; // Optional
// Multisampling (disabled)
@ -96,7 +95,7 @@ Pipeline::Pipeline(Vulkan &vulkan) : vulkan(vulkan) {
VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.sampleShadingEnable = VK_FALSE;
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
multisampling.minSampleShading = 1.0f; // Optional
multisampling.minSampleShading = 1.0F; // Optional
multisampling.pSampleMask = nullptr; // Optional
multisampling.alphaToCoverageEnable = VK_FALSE; // Optional
multisampling.alphaToOneEnable = VK_FALSE; // Optional
@ -138,10 +137,10 @@ Pipeline::Pipeline(Vulkan &vulkan) : vulkan(vulkan) {
colorBlending.logicOp = VK_LOGIC_OP_COPY; // Optional
colorBlending.attachmentCount = 1;
colorBlending.pAttachments = &colorBlendAttachment;
colorBlending.blendConstants[0] = 0.0f; // Optional
colorBlending.blendConstants[1] = 0.0f; // Optional
colorBlending.blendConstants[2] = 0.0f; // Optional
colorBlending.blendConstants[3] = 0.0f; // Optional
colorBlending.blendConstants[0] = 0.0F; // Optional
colorBlending.blendConstants[1] = 0.0F; // Optional
colorBlending.blendConstants[2] = 0.0F; // Optional
colorBlending.blendConstants[3] = 0.0F; // Optional
// Pipeline
@ -202,7 +201,7 @@ VkShaderModule Pipeline::createShaderModule(const std::vector<char> &bytecode) {
// Important - the buffer must be aligned properly. std::vector does that.
createInfo.pCode = reinterpret_cast<const uint32_t *>(bytecode.data());
VkShaderModule shaderModule;
VkShaderModule shaderModule = nullptr;
vulkan.handleVkResult("Could not load shader",
vkCreateShaderModule(vulkan.getDevice(), &createInfo,
nullptr, &shaderModule));
@ -219,5 +218,4 @@ VkPipeline Pipeline::getVk() { return vk; }
VkPipelineLayout Pipeline::getLayout() { return layout; }
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop

View File

@ -2,8 +2,7 @@
#include "vulkan_common.h"
namespace progressia {
namespace desktop {
namespace progressia::desktop {
class Pipeline : public VkObjectWrapper {
@ -23,5 +22,4 @@ class Pipeline : public VkObjectWrapper {
VkPipelineLayout getLayout();
};
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop

View File

@ -3,10 +3,9 @@
#include "vulkan_adapter.h"
#include "vulkan_common.h"
namespace progressia {
namespace desktop {
namespace progressia::desktop {
RenderPass::RenderPass(Vulkan &vulkan) : vulkan(vulkan) {
RenderPass::RenderPass(Vulkan &vulkan) : vk(), vulkan(vulkan) {
std::vector<VkAttachmentDescription> attachmentDescriptions;
std::vector<VkAttachmentReference> attachmentReferences;
@ -15,8 +14,8 @@ RenderPass::RenderPass(Vulkan &vulkan) : vulkan(vulkan) {
for (std::size_t i = 0; i < attachments.size(); i++) {
const auto &attachment = attachments[i];
VkAttachmentDescription *desc;
VkAttachmentReference *ref;
VkAttachmentDescription *desc = nullptr;
VkAttachmentReference *ref = nullptr;
attachmentDescriptions.push_back({});
desc = &attachmentDescriptions.back();
@ -79,5 +78,4 @@ RenderPass::~RenderPass() {
VkRenderPass RenderPass::getVk() { return vk; }
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop

View File

@ -2,8 +2,7 @@
#include "vulkan_common.h"
namespace progressia {
namespace desktop {
namespace progressia::desktop {
class RenderPass : public VkObjectWrapper {
@ -19,5 +18,4 @@ class RenderPass : public VkObjectWrapper {
VkRenderPass getVk();
};
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop

View File

@ -7,23 +7,23 @@
#include "glfw_mgmt_details.h"
#include "vulkan_adapter.h"
#include "vulkan_common.h"
#include "vulkan_physical_device.h"
#include "vulkan_render_pass.h"
#include "../../main/logging.h"
using namespace progressia::main::logging;
namespace progressia {
namespace desktop {
namespace progressia::desktop {
SwapChain::SupportDetails
SwapChain::querySwapChainSupport(VkPhysicalDevice device, Vulkan &vulkan) {
SupportDetails details;
auto surface = vulkan.getSurface().getVk();
auto *surface = vulkan.getSurface().getVk();
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface,
&details.capabilities);
uint32_t formatCount;
uint32_t formatCount = 0;
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount,
nullptr);
@ -33,7 +33,7 @@ SwapChain::querySwapChainSupport(VkPhysicalDevice device, Vulkan &vulkan) {
details.formats.data());
}
uint32_t presentModeCount;
uint32_t presentModeCount = 0;
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface,
&presentModeCount, nullptr);
@ -51,7 +51,8 @@ bool SwapChain::isSwapChainSuitable(const SupportDetails &details) {
}
void SwapChain::create() {
auto details = querySwapChainSupport(vulkan.getPhysicalDevice(), vulkan);
auto details =
querySwapChainSupport(vulkan.getPhysicalDevice().getVk(), vulkan);
auto surfaceFormat = chooseSurfaceFormat(details.formats);
auto presentMode = choosePresentMode(details.presentModes, true);
this->extent = chooseExtent(details.capabilities);
@ -188,6 +189,7 @@ void SwapChain::create() {
}
}
// NOLINTNEXTLINE(readability-convert-member-functions-to-static): future-proofing
VkSurfaceFormatKHR SwapChain::chooseSurfaceFormat(
const std::vector<VkSurfaceFormatKHR> &supported) {
for (const auto &option : supported) {
@ -202,6 +204,7 @@ VkSurfaceFormatKHR SwapChain::chooseSurfaceFormat(
exit(1);
}
// NOLINTNEXTLINE(readability-convert-member-functions-to-static): future-proofing
bool SwapChain::isTripleBufferingSupported(
const std::vector<VkPresentModeKHR> &supported) {
return std::find(supported.begin(), supported.end(),
@ -219,13 +222,15 @@ SwapChain::choosePresentMode(const std::vector<VkPresentModeKHR> &supported,
}
VkExtent2D
// NOLINTNEXTLINE(readability-convert-member-functions-to-static): future-proofing
SwapChain::chooseExtent(const VkSurfaceCapabilitiesKHR &capabilities) {
if (capabilities.currentExtent.width !=
std::numeric_limits<uint32_t>::max()) {
return capabilities.currentExtent;
}
int width, height;
int width = 0;
int height = 0;
glfwGetFramebufferSize(getGLFWWindowHandle(), &width, &height);
VkExtent2D actualExtent = {static_cast<uint32_t>(width),
@ -242,7 +247,7 @@ SwapChain::chooseExtent(const VkSurfaceCapabilitiesKHR &capabilities) {
}
void SwapChain::destroy() {
for (auto framebuffer : framebuffers) {
for (auto *framebuffer : framebuffers) {
vkDestroyFramebuffer(vulkan.getDevice(), framebuffer, nullptr);
}
framebuffers.clear();
@ -259,7 +264,7 @@ void SwapChain::destroy() {
}
}
for (auto colorBufferView : colorBufferViews) {
for (auto *colorBufferView : colorBufferViews) {
vkDestroyImageView(vulkan.getDevice(), colorBufferView, nullptr);
}
colorBufferViews.clear();
@ -271,10 +276,10 @@ void SwapChain::destroy() {
}
SwapChain::SwapChain(Vulkan &vulkan)
: vk(VK_NULL_HANDLE), colorBuffer(nullptr),
colorBufferViews(), extent{0, 0}, depthBuffer(nullptr), framebuffers(),
vulkan(vulkan) {
auto details = querySwapChainSupport(vulkan.getPhysicalDevice(), vulkan);
: vk(VK_NULL_HANDLE), colorBuffer(nullptr), extent{0, 0},
depthBuffer(nullptr), vulkan(vulkan) {
auto details =
querySwapChainSupport(vulkan.getPhysicalDevice().getVk(), vulkan);
auto surfaceFormat = chooseSurfaceFormat(details.formats);
vulkan.getAdapter().getAttachments().push_back(
@ -289,7 +294,7 @@ SwapChain::SwapChain(Vulkan &vulkan)
VK_ATTACHMENT_LOAD_OP_CLEAR,
VK_ATTACHMENT_STORE_OP_STORE,
{{{0.0f, 0.0f, 0.0f, 1.0f}}},
{{{0.0F, 0.0F, 0.0F, 1.0F}}},
std::make_unique<Image>(static_cast<VkImage>(VK_NULL_HANDLE),
static_cast<VkImageView>(VK_NULL_HANDLE),
@ -325,5 +330,4 @@ VkFramebuffer SwapChain::getFramebuffer(std::size_t index) const {
VkExtent2D SwapChain::getExtent() const { return extent; }
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop

View File

@ -3,8 +3,7 @@
#include "vulkan_adapter.h"
#include "vulkan_common.h"
namespace progressia {
namespace desktop {
namespace progressia::desktop {
class SwapChain : public VkObjectWrapper {
@ -54,5 +53,4 @@ class SwapChain : public VkObjectWrapper {
VkExtent2D getExtent() const;
};
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop

View File

@ -1,7 +1,6 @@
#include "vulkan_texture_descriptors.h"
namespace progressia {
namespace desktop {
namespace progressia::desktop {
void TextureDescriptors::allocatePool() {
pools.resize(pools.size() + 1);
@ -16,7 +15,7 @@ void TextureDescriptors::allocatePool() {
poolInfo.pPoolSizes = &poolSize;
poolInfo.maxSets = POOL_SIZE;
auto output = &pools[pools.size() - 1];
auto *output = &pools[pools.size() - 1];
vulkan.handleVkResult(
"Could not create texture descriptor pool",
vkCreateDescriptorPool(vulkan.getDevice(), &poolInfo, nullptr, output));
@ -25,7 +24,7 @@ void TextureDescriptors::allocatePool() {
}
TextureDescriptors::TextureDescriptors(Vulkan &vulkan)
: DescriptorSetInterface(SET_NUMBER, vulkan) {
: DescriptorSetInterface(SET_NUMBER, vulkan), lastPoolCapacity(0) {
VkDescriptorSetLayoutCreateInfo layoutInfo{};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
@ -48,7 +47,7 @@ TextureDescriptors::TextureDescriptors(Vulkan &vulkan)
}
TextureDescriptors::~TextureDescriptors() {
for (auto pool : pools) {
for (auto *pool : pools) {
vkDestroyDescriptorPool(vulkan.getDevice(), pool, nullptr);
}
@ -72,7 +71,7 @@ VkDescriptorSet TextureDescriptors::addTexture(VkImageView view,
allocInfo.descriptorSetCount = 1;
allocInfo.pSetLayouts = &layout;
VkDescriptorSet descriptorSet;
VkDescriptorSet descriptorSet = nullptr;
vulkan.handleVkResult("Could not create texture descriptor set",
vkAllocateDescriptorSets(vulkan.getDevice(),
&allocInfo, &descriptorSet));
@ -102,5 +101,4 @@ VkDescriptorSet TextureDescriptors::addTexture(VkImageView view,
return descriptorSet;
}
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop

View File

@ -5,8 +5,7 @@
#include "vulkan_common.h"
#include "vulkan_descriptor_set.h"
namespace progressia {
namespace desktop {
namespace progressia::desktop {
class TextureDescriptors : public DescriptorSetInterface {
private:
@ -25,5 +24,4 @@ class TextureDescriptors : public DescriptorSetInterface {
VkDescriptorSet addTexture(VkImageView, VkSampler);
};
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop

View File

@ -1,5 +1,6 @@
#pragma once
#include <array>
#include <memory>
#include <vector>
@ -7,8 +8,7 @@
#include "vulkan_common.h"
#include "vulkan_descriptor_set.h"
namespace progressia {
namespace desktop {
namespace progressia::desktop {
template <typename... Entries> class Uniform : public DescriptorSetInterface {
@ -70,7 +70,6 @@ template <typename... Entries> class Uniform : public DescriptorSetInterface {
void doUpdates();
};
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop
#include "vulkan_uniform.inl"

View File

@ -5,14 +5,29 @@
#include "../../main/util.h"
#include "vulkan_frame.h"
#include "vulkan_pipeline.h"
#include "vulkan_physical_device.h"
namespace progressia {
namespace desktop {
namespace progressia::desktop {
namespace detail {
template <typename T>
std::size_t offsetOf(Vulkan &vulkan) {
auto step = vulkan.getPhysicalDevice().getMinUniformOffset();
return ((sizeof(T) - 1) / step + 1) * step; // Round up to multiple
}
template <typename T>
std::size_t offsetOf(Vulkan &vulkan, const T&) {
return offsetOf<T>(vulkan);
}
}
template <typename... Entries>
Uniform<Entries...>::StateImpl::Set::Set(VkDescriptorSet vk, Vulkan &vulkan)
: vk(vk),
contents((sizeof(Entries) + ...), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
contents((detail::offsetOf<Entries>(vulkan) + ...), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
vulkan) {}
@ -48,7 +63,7 @@ Uniform<Entries...>::StateImpl::StateImpl(
writes[index].descriptorCount = 1;
writes[index].pBufferInfo = &bufferInfos[index];
offset += sizeof(Entry);
offset += detail::offsetOf<Entry>(vulkan);
index++;
})
}
@ -71,7 +86,7 @@ void Uniform<Entries...>::State::update(const Entries &...entries) {
auto *dst = state.newContents.data();
FOR_PACK(Entries, entries, e, {
std::memcpy(dst, &e, sizeof(e));
dst += sizeof(e);
dst += detail::offsetOf(uniform->getVulkan(), e);
})
state.setsToUpdate = state.sets.size();
}
@ -190,5 +205,4 @@ template <typename... Entries> void Uniform<Entries...>::doUpdates() {
}
}
} // namespace desktop
} // namespace progressia
} // namespace progressia::desktop

View File

@ -27,30 +27,28 @@ int main(int argc, char *argv[]) {
<< main::meta::VERSION_NUMBER << ")";
debug("Debug is enabled");
desktop::initializeGlfw();
desktop::initializeVulkan();
desktop::showWindow();
auto glfwManager = desktop::makeGlfwManager();
desktop::VulkanManager vulkanManager;
glfwManager->setOnScreenResize([&]() { vulkanManager.resizeSurface(); });
glfwManager->showWindow();
main::initialize(desktop::getVulkan()->getGint());
auto game = main::makeGame(vulkanManager.getVulkan()->getGint());
info("Loading complete");
while (desktop::shouldRun()) {
bool abortFrame = !desktop::startRender();
while (glfwManager->shouldRun()) {
bool abortFrame = !vulkanManager.startRender();
if (abortFrame) {
continue;
}
main::renderTick();
game->renderTick();
desktop::endRender();
desktop::doGlfwRoutine();
vulkanManager.endRender();
glfwManager->doGlfwRoutine();
}
info("Shutting down");
desktop::getVulkan()->waitIdle();
main::shutdown();
desktop::shutdownVulkan();
desktop::shutdownGlfw();
vulkanManager.getVulkan()->waitIdle();
return 0;
}

View File

@ -1,85 +1,192 @@
# Building guide
# Building Guide
At this time, building is only supported in GNU/Linux targeting GNU/Linux with
X11/Wayland and Windows (cross-compilation). See also
[Development Setup Guide](DevelopmentSetupGuide.md)
if you want to make git commits.
This document provides instructions for building Progressia from source code.
See also
[Development Setup Guide](DevelopmentSetupGuide.md)
and
[IDE setup guides](ide_setup).
MacOS targets are not supported at this moment.
## Short version
Debian/Ubuntu:
```bash
# Install GCC, CMake, Python 3, glslc, git, Vulkan, GLFW and GLM
sudo apt update && sudo apt install -y \
g++ cmake python3 glslc git libvulkan-dev libglfw3-dev libglm-dev
# Clone project
git clone https://github.com/Wind-Corporation/Progressia.git
cd Progressia
# Generate build files for release (MY-1 is the build ID)
cmake -S . -B build -DBUILD_ID=MY-1 -DCMAKE_BUILD_TYPE=Release
# Compile
cmake --build build
# Run
build/progressia
```
Fedora:
```bash
# Install GCC, CMake, Python 3, glslc, git, Vulkan, GLFW and GLM
sudo dnf install -y \
gcc-c++ cmake python3 glslc git vulkan-devel glfw-devel glm-devel
# Clone project
git clone https://github.com/Wind-Corporation/Progressia.git
cd Progressia
# Generate build files for release (MY-1 is the build ID)
cmake -S . -B build -DBUILD_ID=MY-1 -DCMAKE_BUILD_TYPE=Release
# Compile
cmake --build build
# Run
build/progressia
```
Windows: _see [IDE setup guides](ide_setup)._
## Prerequisites
Install the following software:
- a C++ compiler (GCC or clang preferably),
- CMake,
- Python 3,
- glslc.
### C++ compiler
Install the following libraries with headers:
- Vulkan (loader library and headers),
- GLFW3,
- GLM,
- Boost (only core library required).
Project explicitly fully supports GCC, MinGW and Clang. Compilation with MSVC
is also supported, but it can't be used for release builds and its use is
generally discouraged.
### Debian
On Windows,
[w64devkit](https://github.com/skeeto/w64devkit/releases)
distribution of MinGW was tested.
On Debian, you can run the following commands as root to install almost all
required software:
Cross-compilation from Linux to Windows is also explicitly supported with
MinGW-w64 as provided by Debian.
### CMake
[CMake](https://cmake.org/) version 3.12 or higher is required.
### Python 3
[Python 3](https://www.python.org/downloads/) is required.
### Vulkan
The following Vulkan components are strictly necessary for builds:
- Header files
- Loader static library (`vulkan-1.lib`)
- `glslc` ([standalone downloads](https://github.com/google/shaderc/blob/main/downloads.md))
However, it is usually easier to install a complete Vulkan SDK. An open-source
Vulkan SDK can be downloaded from
[LunarG](https://www.lunarg.com/vulkan-sdk/)
for all platforms.
Debian/Ubuntu users can install this dependency using APT:
```bash
apt-get install \
g++ \
cmake \
python3 &&
apt-get install --no-install-recommends \
libvulkan-dev \
libglfw3-dev \
libglm-dev \
libboost-dev
apt install libvulkan-dev glslc
```
However, glslc, the shader compiler, is not available as a Debian package at the
moment. You can install it manually from official sources or use the download it
from windcorp.ru by running these commands as root:
Fedora users can install this dependency using dnf:
```bash
apt-get install wget &&
mkdir -p /opt/glslc &&
wget --output-file=/opt/glslc/glslc \
'https://windcorp.ru/other/glslc-v2022.1-6-ga0a247d-static' &&
chmod +x /opt/glslc/glslc
dnf install vulkan-devel glslc
```
Alternatively, packages provided by LunarG are available for Ubuntu. Follow the
instructions on [LunarG.com](https://vulkan.lunarg.com/sdk/home) to install
`vulkan-sdk`.
Windows users using vcpkg should install the LunarG distribution, then install
the `vulkan` vcpkg package:
```cmd
vcpkg install vulkan:x64-mingw-static
```
## Setup
### Other libraries
The following libraries are additionally required:
- [GLFW](https://www.glfw.org/download.html) version 3.3.2 or higher
- [GLM](https://glm.g-truc.net/)
Debian/Ubuntu users can install these dependencies using APT:
```bash
apt install libglfw3-dev libglm-dev
```
Fedora users can install these dependencies using dnf:
```bash
dnf install glfw-devel glm-devel
```
Windows users can install these dependencies using vcpkg:
```cmd
vcpkg install glfw3:x64-mingw-static glm:x64-mingw-static
```
## Downloading source code
Clone this git repository.
Command line users: run
```bash
git clone <clone url>
cd Progressia
chmod +x tools/setup.sh
tools/setup.sh
```
`tools/setup.sh` will check the availability of all required commands and
libraries.
Build tools use enviroment variables `PATH`, `VULKAN_SDK`, `CMAKE_MODULE_PATH`
and `CMAKE_PREFIX_PATH` to locate the various components; you can edit these
variables system-wide or use `tools/private.sh` to amend them for build tools.
(Your changes to `tools/private.sh` are ignored by git.)
For example, of you ran the script to download glslc on Debian, you will need to
add the following line to `tools/private.sh`:
```bash
PATH="$PATH:/opt/glslc"
```
## Building
### CMake
Use CMake to generate build files. There are a few options available:
- **`BUILD_ID`** enables release builds and specifies visible unique build
identifier string.
- `DEV_MODE`, `VULKAN_ERROR_CHECKING`:
see [Development Setup Guide](DevelopmentSetupGuide.md).
- `VULKAN_ERROR_CHECKING` enables Vulkan debug features. This requires Vulkan
validation layers (available as part of LunarG Vulkan SDK,
`vulkan-validationlayers-dev` Debian package and `vulkan-devel` Fedora
package).
Directory `build` in project root is ignored by git for convenience.
This step is usually performed in the IDE.
Command line users: run
```bash
tools/build.sh
cd /path/to/project
# Routine (debug) build
cmake -S . -B build
# Release build
cmake -S . -B build -DBUILD_ID=MY-1 -DCMAKE_BUILD_TYPE=Release
```
> **Note**
>
> Use proper build IDs if distribution is expected. Convention is two-letter
> builder identifier, a dash and a unique ascending build number.
>
> For example, automated builds at windcorp.ru use IDs `WA-1`, `WA-2`, etc.
> **Note**
>
> Release builds with MSVC are not supported.
> The standard library used by MSVC poses a problem:
> - it cannot be statically linked with Progressia due to GPL restrictions,
> - it cannot be bundled with Progressia for the same reason,
> - asking the user to install Visual C++ Runtime manually would introduce
> unnecessary confusion because official builds do not require it.
### Compiling
This step is usually performed in the IDE.
Command line users: run
```bash
cmake --build build
```
## Running
```bash
tools/build.sh -R
```
Executable file will be located directly inside the CMake binary directory.
Directory `run` in project root is ignored by git for convenience; using
project root as working directory is safe for debug builds.

View File

@ -1,63 +1,93 @@
# Development setup guide
To make development easier, contributors should be using a few tools. Included
with the project are configurations and scripts for these tools:
- [cppcheck](http://cppcheck.net/) performs static code analysis for C++
- [clang-format](https://clang.llvm.org/docs/ClangFormat.html) automatically
formats C++ source code
- [memcheck](https://valgrind.org/docs/manual/mc-manual.html)
(part of [valgrind](https://valgrind.org/)) performs runtime memory
error detection
This document provides instructions for setting up a development environment
for Progressia.
See also
[Building Guide](BuildingGuide.md)
and
[IDE setup guides](ide_setup).
Additionally, git hooks prevent committing code that is formatted incorrectly,
does not compile or produces warnings. You can bypass this check using
To make development easier, contributors should be using a few tools. Included
with the project are configurations and scripts for these tools:
- [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) performs static
code analysis for C++
- [clang-format](https://clang.llvm.org/docs/ClangFormat.html) formats C++
source code
- Vulkan validation layers checks for errors in Vulkan API usage at runtime
(see below for details)
- [memcheck](https://valgrind.org/docs/manual/mc-manual.html)
(part of [valgrind](https://valgrind.org/)) performs runtime memory
error detection on Linux
Additionally, a git pre-commit hook prevents committing code that is formatted
incorrectly, does not compile or produces warnings.
## Basic setup
Follow [Building Guide](BuildingGuide.md) instructions before proceeding.
Debian/Ubuntu:
```bash
# Install clang-tidy and clang-format-diff
sudo apt update && sudo apt install -y \
clang-tidy clang-format
# Enable DEV_MODE (sets up git pre-commit hook)
cmake -S . -B build -DDEV_MODE=ON
```
Fedora:
```bash
# Install clang-tidy and clang-format-diff
sudo dnf install -y \
clang-tools-extra clang
# Enable DEV_MODE (sets up git pre-commit hook)
cmake -S . -B build -DDEV_MODE=ON
```
Windows: _see [IDE setup guides](ide_setup)._
## Pre-commit git hook
A
[git pre-commit hook](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks)
is installed to correct formatting and check for compilation/linting issues.
This check can be bypassed with
`git commit --no-verify`
in case of dire need.
## Prerequisites
The hook runs `tools/pre-commit.py`, which formats modified files and ensures
that `cmake --build build` executes without errors. Several git operations are
performed by `pre-commit.py`; run
`tools/pre-commit.py restore`
to restore the state of the repository in case the hook crashes.
Perform the setup described in the [Building Guide](BuildingGuide.md) first.
The list of directories with source code to format and the list of source code
filename extensions are hard-coded into the Python script.
Install the following software:
- cppcheck,
- clang-format (version 13 is recommended)
- valgrind
## Vulkan validation layers
### Debian
On Debian, you can run the following commands as root to install all required
software:
```bash
apt-get install \
cppcheck \
clang-format-13 \
valgrind
```
## Setup
```bash
tools/setup.sh --for-development
```
With `--for-development` flag, `tools/setup.sh` will check the development tools
and install git pre-commit hook in addition to its normal duties.
## Notes
Developers will find it useful to read through the following help pages:
```bash
tools/build.sh --help
tools/cppcheck/use-cppcheck.sh --help
tools/clang-format/use-clang-format.sh --help
```
LunarG validation layers are extremely useful when writing and debugging Vulkan.
LunarG validation layers are extremely useful when debugging Vulkan code.
The official
[Vulkan tutorial](https://vulkan-tutorial.com/Development_environment)
has detailed instructions for all platforms.
In particular, Debian users can run the following command as root:
Use CMake option `VULKAN_ERROR_CHECKING` to enable the use of validation
layers.
Debian/Ubuntu users can install this dependency using APT:
```bash
apt-get install vulkan-validationlayers-dev
apt install vulkan-validationlayers-dev
```
Fedora users can install this dependency using dnf:
```bash
dnf install vulkan-validation-layers-devel
```
Windows users can install this dependency when installing LunarG distribution.
## memcheck
`tools/valgrind-memcheck-suppressions.supp` contains useful suppressions for
memcheck.

View File

@ -0,0 +1,100 @@
# IDE setup guide: Windows / CLion
> **Note**
>
> This guide has not been tested sufficiently because currently none of the
> developers use CLion to develop Progressia. Please let us know if this guide
> requires corrections or updates.
This document is an IDE setup guide for CLion with MinGW, the recommended
compiler for Windows.
Compilation with MSVC and clang-cl is supported; however, these compilers may
generate warnings. Additionally, release builds compiled with MSVC or clang-cl
are strongly discouraged, see [Building Guide](../BuildingGuide.md).
## Installing CLion
Install CLion as usual. Close CLion for the following steps.
> **Note**
>
> Native vcpkg support has been added to CLion in version 2023.1. At the time
> of writing this is a recent update. Make sure you use the latest version.
>
> Workaround for older versions: add
> `-DCMAKE_TOOLCHAIN_FILE=/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake`
> to CMake options.
## Installing build tools
### Python 3
Install Python 3 (available from
[the official website](https://www.python.org/downloads/)
and Microsoft Store). Make sure `python` or `python3` is available in PATH:
```cmd
:: This command must work in a fresh CMD:
python3 --version
```
Note that if running this command launches Microsoft Store, Python was not
installed correctly.
### MinGW
Install MinGW. There are many distributions of MinGW available; this guide was
tested with [w64devkit](https://github.com/skeeto/w64devkit).
To install w64devkit, go to the
[Releases](https://github.com/skeeto/w64devkit/releases)
section of the official repository. Download the `w64devkit-XXX.zip` file and
extract it into `C:\msys64\mingw64\`. If extracted correctly,
`C:\msys64\mingw64\bin\gcc.exe`
should exist. Directory
`C:\msys64\mingw64\bin\`
should be added to system PATH
([instructions for Windows 10](https://stackoverflow.com/a/44272417/4463352)).
Proper installation can be verified like so:
```cmd
:: This command must work in a fresh CMD:
gcc --version
```
## Installing libraries
Several third party libraries are used by the project. With Windows, installing
them manually can be a hassle, so the developers recommend using vcpkg.
A Vulkan SDK has to be installed before vcpkg can install `vulkan` package.
[LunarG](https://www.lunarg.com/vulkan-sdk/) distribution is recommended:
download and run the SDK installer. "Validation layer" errors are common on
Windows and can usually be safely ignored; they are typically caused by third-
party software such as GPU drivers, OBS or Steam.
To install vcpkg, go to the
[Releases](https://github.com/microsoft/vcpkg/releases) section of the official
repository. Download and extract "Source code" ZIP file to a directory of your
choice. Run the following commands inside the resulting folder:
```cmd
:: Perform initial setup
bootstrap-vcpkg
:: Setup Visual Studio integration
vcpkg integrate install
:: Install libraries
vcpkg install vulkan:x64-mingw-static glfw3:x64-mingw-static glm:x64-mingw-static
```
## Project setup
Uhm... how do I put it... I could not get my hands on a Windows install of
CLion in a reasonable time and so I will have to leave this blank for now. If
you have CLion on Windows, please contact the devs so we can do the setup
together and this doc can be completed.
In general, from this point you should clone the git repo and open the project
as a CMake project.
## Developer setup
To enable features useful for developers, set CMake option `DEV_MODE` to `ON`.
See [Development Setup Guide](../DevelopmentSetupGuide.md) for more details.

View File

@ -0,0 +1,97 @@
# IDE setup guide: Windows / Visual Studio
This document is an IDE setup guide for Visual Studio with MinGW, the
recommended compiler for Windows.
Compilation with MSVC and clang-cl is supported; however, these compilers may
generate warnings. Additionally, release builds compiled with MSVC or clang-cl
are strongly discouraged, see [Building Guide](../BuildingGuide.md).
## Installing Visual Studio
This guide was tested with Visual Studio 2022. "Desktop Development with C++"
workload is required to work with C++ projects. Launch Visual Studio at least
once with this configuration and close it for the next steps.
## Installing build tools
### Python 3
Install Python 3 (available from
[the official website](https://www.python.org/downloads/)
and Microsoft Store). Make sure `python` or `python3` is available in PATH:
```cmd
:: This command must work in a fresh CMD:
python3 --version
```
Note that if running this command launches Microsoft Store, Python was not
installed correctly.
### MinGW
Install MinGW. There are many distributions of MinGW available; this guide was
tested with [w64devkit](https://github.com/skeeto/w64devkit).
To install w64devkit, go to the
[Releases](https://github.com/skeeto/w64devkit/releases)
section of the official repository. Download the `w64devkit-XXX.zip` file and
extract it into `C:\msys64\mingw64\`. If extracted correctly,
`C:\msys64\mingw64\bin\gcc.exe`
should exist. Directory
`C:\msys64\mingw64\bin\`
should be added to system PATH
([instructions for Windows 10](https://stackoverflow.com/a/44272417/4463352)).
Proper installation can be verified like so:
```cmd
:: This command must work in a fresh CMD:
gcc --version
```
## Installing libraries
Several third party libraries are used by the project. With Windows, installing
them manually can be a hassle, so the developers recommend using vcpkg.
A Vulkan SDK has to be installed before vcpkg can install `vulkan` package.
[LunarG](https://www.lunarg.com/vulkan-sdk/) distribution is recommended:
download and run the SDK installer. "Validation layer" errors are common on
Windows and can usually be safely ignored; they are typically caused by third-
party software such as GPU drivers, OBS or Steam.
To install vcpkg, go to the
[Releases](https://github.com/microsoft/vcpkg/releases) section of the official
repository. Download and extract "Source code" ZIP file to a directory of your
choice. Run the following commands inside the resulting folder:
```cmd
:: Perform initial setup
bootstrap-vcpkg
:: Setup Visual Studio integration
vcpkg integrate install
:: Install libraries
vcpkg install vulkan:x64-mingw-static glfw3:x64-mingw-static glm:x64-mingw-static
```
## Project setup
Start Visual Studio. Use "Clone a Repository" to download sources and create a
project. Select the project in Solution Explorer and wait for CMake
initialization to complete.
Next, click on "x64-Debug" in the toolbar. Click on "Manage Configurations..."
to open CMake Settings. Use the plus button to add a new configuration; select
"Mingw64-Debug" when prompted. Select the new configuration and add the
following parameter to "CMake command arguments":
```
-DVCPKG_TARGET_TRIPLET=x64-mingw-static
```
Remove "x64-Debug" configuration by selecting it and pressing the cross button.
Finally click "▶ Select startup item" in the toolbar and choose progressia.exe.
## Developer setup
To enable features useful for developers, set CMake option `DEV_MODE` to `ON`.
See [Development Setup Guide](../DevelopmentSetupGuide.md) for more details.
TODO: _include step-by-step instructions for this section._

View File

@ -1,5 +1,6 @@
#include "game.h"
#include <array>
#include <iostream>
#define GLM_FORCE_RADIANS
@ -15,167 +16,181 @@
#include "logging.h"
using namespace progressia::main::logging;
namespace progressia {
namespace main {
namespace progressia::main {
std::unique_ptr<Primitive> cube1, cube2;
std::unique_ptr<Texture> texture1, texture2;
std::unique_ptr<View> perspective;
std::unique_ptr<Light> light;
class GameImpl : public Game {
GraphicsInterface *gint;
DISABLE_COPYING(GameImpl)
DISABLE_MOVING(GameImpl)
void addRect(glm::vec3 origin, glm::vec3 width, glm::vec3 height,
glm::vec4 color, std::vector<Vertex> &vertices,
std::vector<Vertex::Index> &indices) {
public:
std::unique_ptr<Primitive> cube1;
std::unique_ptr<Primitive> cube2;
std::unique_ptr<Texture> texture1;
std::unique_ptr<Texture> texture2;
std::unique_ptr<View> perspective;
std::unique_ptr<Light> light;
Vertex::Index offset = vertices.size();
GraphicsInterface *gint;
vertices.push_back({origin, color, {}, {0, 0}});
vertices.push_back({origin + width, color, {}, {0, 1}});
vertices.push_back({origin + width + height, color, {}, {1, 1}});
vertices.push_back({origin + height, color, {}, {1, 0}});
static void addRect(glm::vec3 origin, glm::vec3 width, glm::vec3 height,
glm::vec4 color, std::vector<Vertex> &vertices,
std::vector<Vertex::Index> &indices) {
indices.push_back(offset + 0);
indices.push_back(offset + 1);
indices.push_back(offset + 2);
Vertex::Index offset = vertices.size();
indices.push_back(offset + 0);
indices.push_back(offset + 2);
indices.push_back(offset + 3);
}
vertices.push_back({origin, color, {}, {0, 0}});
vertices.push_back({origin + width, color, {}, {0, 1}});
vertices.push_back({origin + width + height, color, {}, {1, 1}});
vertices.push_back({origin + height, color, {}, {1, 0}});
void addBox(glm::vec3 origin, glm::vec3 length, glm::vec3 height,
glm::vec3 depth, std::array<glm::vec4, 6> colors,
std::vector<Vertex> &vertices,
std::vector<Vertex::Index> &indices) {
addRect(origin, height, length, colors[0], vertices, indices);
addRect(origin, length, depth, colors[1], vertices, indices);
addRect(origin, depth, height, colors[2], vertices, indices);
addRect(origin + height, depth, length, colors[3], vertices, indices);
addRect(origin + length, height, depth, colors[4], vertices, indices);
addRect(origin + depth, length, height, colors[5], vertices, indices);
}
indices.push_back(offset + 0);
indices.push_back(offset + 1);
indices.push_back(offset + 2);
void initialize(GraphicsInterface &gintp) {
indices.push_back(offset + 0);
indices.push_back(offset + 2);
indices.push_back(offset + 3);
}
debug("game init begin");
gint = &gintp;
static void addBox(glm::vec3 origin, glm::vec3 length, glm::vec3 height,
glm::vec3 depth, std::array<glm::vec4, 6> colors,
std::vector<Vertex> &vertices,
std::vector<Vertex::Index> &indices) {
addRect(origin, height, length, colors[0], vertices, indices);
addRect(origin, length, depth, colors[1], vertices, indices);
addRect(origin, depth, height, colors[2], vertices, indices);
addRect(origin + height, depth, length, colors[3], vertices, indices);
addRect(origin + length, height, depth, colors[4], vertices, indices);
addRect(origin + depth, length, height, colors[5], vertices, indices);
}
texture1.reset(gint->newTexture(
progressia::main::loadImage(u"../assets/texture.png")));
texture2.reset(gint->newTexture(
progressia::main::loadImage(u"../assets/texture2.png")));
GameImpl(GraphicsInterface &gintp) {
// Cube 1
{
std::vector<Vertex> vertices;
std::vector<Vertex::Index> indices;
auto white = glm::vec4(1, 1, 1, 1);
debug("game init begin");
gint = &gintp;
addBox({-0.5, -0.5, -0.5}, {1, 0, 0}, {0, 1, 0}, {0, 0, 1},
{white, white, white, white, white, white}, vertices, indices);
texture1 =
gint->newTexture(progressia::main::loadImage("assets/texture.png"));
texture2 = gint->newTexture(
progressia::main::loadImage("assets/texture2.png"));
for (std::size_t i = 0; i < indices.size(); i += 3) {
Vertex &a = vertices[indices[i + 0]];
Vertex &b = vertices[indices[i + 1]];
Vertex &c = vertices[indices[i + 2]];
// Cube 1
{
std::vector<Vertex> vertices;
std::vector<Vertex::Index> indices;
auto white = glm::vec4(1, 1, 1, 1);
glm::vec3 x = b.position - a.position;
glm::vec3 y = c.position - a.position;
addBox({-0.5, -0.5, -0.5}, {1, 0, 0}, {0, 1, 0}, {0, 0, 1},
{white, white, white, white, white, white}, vertices,
indices);
glm::vec3 normal = glm::normalize(glm::cross(x, y));
for (std::size_t i = 0; i < indices.size(); i += 3) {
Vertex &a = vertices[indices[i + 0]];
Vertex &b = vertices[indices[i + 1]];
Vertex &c = vertices[indices[i + 2]];
a.normal = normal;
b.normal = normal;
c.normal = normal;
glm::vec3 x = b.position - a.position;
glm::vec3 y = c.position - a.position;
glm::vec3 normal = glm::normalize(glm::cross(x, y));
a.normal = normal;
b.normal = normal;
c.normal = normal;
}
cube1 = gint->newPrimitive(vertices, indices, &*texture1);
}
cube1.reset(gint->newPrimitive(vertices, indices, &*texture1));
}
// Cube 2
{
std::vector<Vertex> vertices;
std::vector<Vertex::Index> indices;
auto white = glm::vec4(1, 1, 1, 1);
// Cube 2
{
std::vector<Vertex> vertices;
std::vector<Vertex::Index> indices;
auto white = glm::vec4(1, 1, 1, 1);
addBox({-0.5, -2.5, -0.5}, {1, 0, 0}, {0, 1, 0}, {0, 0, 1},
{white, white, white, white, white, white}, vertices,
indices);
addBox({-0.5, -2.5, -0.5}, {1, 0, 0}, {0, 1, 0}, {0, 0, 1},
{white, white, white, white, white, white}, vertices, indices);
for (std::size_t i = 0; i < indices.size(); i += 3) {
Vertex &a = vertices[indices[i + 0]];
Vertex &b = vertices[indices[i + 1]];
Vertex &c = vertices[indices[i + 2]];
for (std::size_t i = 0; i < indices.size(); i += 3) {
Vertex &a = vertices[indices[i + 0]];
Vertex &b = vertices[indices[i + 1]];
Vertex &c = vertices[indices[i + 2]];
glm::vec3 x = b.position - a.position;
glm::vec3 y = c.position - a.position;
glm::vec3 x = b.position - a.position;
glm::vec3 y = c.position - a.position;
glm::vec3 normal = glm::normalize(glm::cross(x, y));
glm::vec3 normal = glm::normalize(glm::cross(x, y));
a.normal = normal;
b.normal = normal;
c.normal = normal;
}
a.normal = normal;
b.normal = normal;
c.normal = normal;
cube2 = gint->newPrimitive(vertices, indices, &*texture2);
}
cube2.reset(gint->newPrimitive(vertices, indices, &*texture2));
perspective = gint->newView();
light = gint->newLight();
debug("game init complete");
}
perspective.reset(gint->newView());
light.reset(gint->newLight());
void renderTick() override {
debug("game init complete");
}
{
float fov = 70.0F;
void renderTick() {
auto extent = gint->getViewport();
auto proj = glm::perspective(
glm::radians(fov), extent.x / (float)extent.y, 0.1F, 10.0F);
proj[1][1] *= -1;
{
float fov = 70.0f;
auto view = glm::lookAt(glm::vec3(2.0F, 2.0F, 2.0F),
glm::vec3(0.0F, 0.0F, 0.0F),
glm::vec3(0.0F, 0.0F, 1.0F));
auto extent = gint->getViewport();
auto proj = glm::perspective(glm::radians(fov),
extent.x / (float)extent.y, 0.1f, 10.0f);
proj[1][1] *= -1;
perspective->configure(proj, view);
}
auto view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f),
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(0.0f, 0.0f, 1.0f));
perspective->use();
perspective->configure(proj, view);
float contrast = glm::sin(gint->tmp_getTime() / 3) * 0.18F + 0.18F;
glm::vec3 color0(0.60F, 0.60F, 0.70F);
glm::vec3 color1(1.10F, 1.05F, 0.70F);
auto m =
static_cast<float>(glm::sin(gint->tmp_getTime() / 3) * 0.5 + 0.5);
glm::vec3 color = m * color1 + (1 - m) * color0;
light->configure(color, glm::vec3(1.0F, -2.0F, 1.0F), contrast, 0.1F);
light->use();
auto model = glm::eulerAngleYXZ(0.0F, 0.0F, gint->tmp_getTime() * 0.1F);
gint->setModelTransform(model);
cube1->draw();
cube2->draw();
}
perspective->use();
~GameImpl() override {
debug("game shutdown begin");
float contrast = glm::sin(gint->tmp_getTime() / 3) * 0.18f + 0.18f;
glm::vec3 color0(0.60f, 0.60f, 0.70f);
glm::vec3 color1(1.10f, 1.05f, 0.70f);
cube1.reset();
cube2.reset();
texture1.reset();
texture2.reset();
float m = glm::sin(gint->tmp_getTime() / 3) * 0.5 + 0.5;
glm::vec3 color = m * color1 + (1 - m) * color0;
light.reset();
perspective.reset();
light->configure(color, glm::vec3(1.0f, -2.0f, 1.0f), contrast, 0.1f);
light->use();
debug("game shutdown complete");
}
};
auto model = glm::eulerAngleYXZ(0.0f, 0.0f, gint->tmp_getTime() * 0.1f);
gint->setModelTransform(model);
cube1->draw();
cube2->draw();
std::unique_ptr<Game> makeGame(GraphicsInterface &gint) {
return std::make_unique<GameImpl>(gint);
}
void shutdown() {
debug("game shutdown begin");
cube1.reset();
cube2.reset();
texture1.reset();
texture2.reset();
light.reset();
perspective.reset();
debug("game shutdown complete");
}
} // namespace main
} // namespace progressia
} // namespace progressia::main

View File

@ -1,13 +1,16 @@
#pragma once
#include "rendering.h"
#include "util.h"
namespace progressia {
namespace main {
namespace progressia::main {
void initialize(GraphicsInterface &);
void renderTick();
void shutdown();
class Game : private NonCopyable {
public:
virtual ~Game() = default;
virtual void renderTick() = 0;
};
} // namespace main
} // namespace progressia
std::unique_ptr<Game> makeGame(GraphicsInterface &);
} // namespace progressia::main

View File

@ -8,8 +8,7 @@
#include <mutex>
#include <sstream>
namespace progressia {
namespace main {
namespace progressia::main {
namespace detail {
@ -21,7 +20,7 @@ class LogSinkBackend {
void flush();
public:
LogSinkBackend() {}
LogSinkBackend() = default;
std::ostream &getOutput() { return buffer; }
@ -46,14 +45,17 @@ class LogSinkBackend {
namespace {
std::ofstream openLogFile() {
// FIXME this is relative to bin, not root dir
std::filesystem::create_directories("../run");
std::filesystem::create_directories("../run/logs");
return std::ofstream("../run/logs/latest.log");
std::filesystem::create_directories("run");
std::filesystem::create_directories("run/logs");
return std::ofstream("run/logs/latest.log");
}
} // namespace
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables): TODO
std::mutex logFileMutex;
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
std::ofstream logFile = openLogFile();
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
thread_local detail::LogSinkBackend theBackend;
std::ostream &detail::LogSink::getStream() const {
@ -78,7 +80,7 @@ detail::LogSink::~LogSink() {
}
}
detail::LogSink::LogSink(LogSink &&moveFrom)
detail::LogSink::LogSink(LogSink &&moveFrom) noexcept
: isCurrentSink(moveFrom.isCurrentSink) {
moveFrom.isCurrentSink = false;
}
@ -99,6 +101,7 @@ void detail::LogSinkBackend::flush() {
namespace {
// FIXME This approach is horribly inefficient. It is also unsafe if any
// other piece of code wants access to std::localtime.
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
std::mutex getLocalTimeMutex;
std::tm getLocalTimeAndDontExplodePlease() {
std::lock_guard<std::mutex> lock(getLocalTimeMutex);
@ -165,5 +168,4 @@ detail::LogSink fatal(const char *start) { return log(LogLevel::FATAL, start); }
} // namespace logging
} // namespace main
} // namespace progressia
} // namespace progressia::main

View File

@ -1,14 +1,13 @@
#pragma once
#include "boost/core/noncopyable.hpp"
#include "util.h"
#include <ostream>
namespace progressia {
namespace main {
namespace progressia::main {
namespace detail {
class LogSink : private boost::noncopyable {
class LogSink : private progressia::main::NonCopyable {
private:
bool isCurrentSink;
@ -24,7 +23,7 @@ class LogSink : private boost::noncopyable {
LogSink(bool isCurrentSink);
~LogSink();
LogSink(LogSink &&);
LogSink(LogSink &&) noexcept;
template <typename T>
friend const LogSink &operator<<(const LogSink &sink, const T &x) {
@ -60,5 +59,4 @@ detail::LogSink fatal(const char *start = nullptr);
void initializeLogging();
void shutdownLogging();
} // namespace main
} // namespace progressia
} // namespace progressia::main

View File

@ -1,10 +1,9 @@
#pragma once
#include "../config.h"
#include <config.h>
#include <cstdlib>
namespace progressia {
namespace main {
namespace progressia::main {
namespace meta {
namespace detail {
@ -37,5 +36,4 @@ constexpr uint32_t VERSION_MINOR = (VERSION_NUMBER & 0x00FF00) >> 8;
constexpr uint32_t VERSION_PATCH = (VERSION_NUMBER & 0x0000FF) >> 0;
} // namespace meta
} // namespace main
} // namespace progressia
} // namespace progressia::main

View File

@ -3,6 +3,4 @@
#include "rendering/graphics_interface.h"
#include "rendering/image.h"
namespace progressia {
namespace main {} // namespace main
} // namespace progressia
namespace progressia::main {} // namespace progressia::main

View File

@ -1,6 +1,6 @@
#pragma once
#include "boost/core/noncopyable.hpp"
#include "../util.h"
#include <vector>
#define GLM_FORCE_RADIANS
@ -12,8 +12,7 @@
#include "image.h"
namespace progressia {
namespace main {
namespace progressia::main {
struct Vertex {
@ -25,31 +24,27 @@ struct Vertex {
glm::vec2 texCoord;
};
class Texture : private boost::noncopyable {
public:
using Backend = void *;
class Texture : private progressia::main::NonCopyable {
private:
Backend backend;
struct Backend;
std::unique_ptr<Backend> backend;
friend class GraphicsInterface;
friend class Primitive;
public:
Texture(Backend);
Texture(std::unique_ptr<Backend>);
~Texture();
};
class Primitive : private boost::noncopyable {
public:
using Backend = void *;
class Primitive : private progressia::main::NonCopyable {
private:
Backend backend;
struct Backend;
std::unique_ptr<Backend> backend;
friend class GraphicsInterface;
public:
Primitive(Backend);
Primitive(std::unique_ptr<Backend>);
~Primitive();
void draw();
@ -57,30 +52,28 @@ class Primitive : private boost::noncopyable {
const Texture *getTexture() const;
};
class View : private boost::noncopyable {
public:
using Backend = void *;
class View : private progressia::main::NonCopyable {
private:
Backend backend;
struct Backend;
std::unique_ptr<Backend> backend;
friend class GraphicsInterface;
public:
View(Backend);
View(std::unique_ptr<Backend>);
~View();
void configure(const glm::mat4 &proj, const glm::mat4 &view);
void use();
};
class Light : private boost::noncopyable {
public:
using Backend = void *;
class Light : private progressia::main::NonCopyable {
private:
Backend backend;
struct Backend;
std::unique_ptr<Backend> backend;
friend class GraphicsInterface;
public:
Light(Backend);
Light(std::unique_ptr<Backend>);
~Light();
void configure(const glm::vec3 &color, const glm::vec3 &from,
@ -88,7 +81,7 @@ class Light : private boost::noncopyable {
void use();
};
class GraphicsInterface : private boost::noncopyable {
class GraphicsInterface : private progressia::main::NonCopyable {
public:
using Backend = void *;
@ -99,18 +92,18 @@ class GraphicsInterface : private boost::noncopyable {
GraphicsInterface(Backend);
~GraphicsInterface();
Texture *newTexture(const Image &);
std::unique_ptr<Texture> newTexture(const Image &);
Primitive *newPrimitive(const std::vector<Vertex> &,
const std::vector<Vertex::Index> &,
Texture *texture);
std::unique_ptr<Primitive> newPrimitive(const std::vector<Vertex> &,
const std::vector<Vertex::Index> &,
Texture *texture);
glm::vec2 getViewport() const;
void setModelTransform(const glm::mat4 &);
View *newView();
Light *newLight();
std::unique_ptr<View> newView();
std::unique_ptr<Light> newLight();
void flush();
void startNextLayer();
@ -119,5 +112,4 @@ class GraphicsInterface : private boost::noncopyable {
uint64_t getLastStartedFrame();
};
} // namespace main
} // namespace progressia
} // namespace progressia::main

View File

@ -4,15 +4,17 @@
#include <filesystem>
#include <fstream>
#include <iostream>
#include <limits>
#include <vector>
#include "stb/stb_image.h"
#include <embedded_resources.h>
#include "../logging.h"
using namespace progressia::main::logging;
namespace progressia {
namespace main {
namespace progressia::main {
std::size_t Image::getSize() const { return data.size(); }
@ -20,33 +22,36 @@ const Image::Byte *Image::getData() const { return data.data(); }
Image::Byte *Image::getData() { return data.data(); }
Image loadImage(const std::filesystem::path &path) {
Image loadImage(const std::string &path) {
std::ifstream file(path, std::ios::ate | std::ios::binary);
auto resource = __embedded_resources::getEmbeddedResource(path);
if (!file.is_open()) {
fatal() << "Could not access a PNG image in file " << path;
if (resource.data == nullptr) {
// REPORT_ERROR
progressia::main::logging::fatal()
<< "Could not find resource \"" << path << "\"";
exit(1);
}
std::size_t fileSize = static_cast<std::size_t>(file.tellg());
std::vector<Image::Byte> png(fileSize);
std::vector<Image::Byte> png(resource.data,
resource.data + resource.length);
file.seekg(0);
file.read(reinterpret_cast<char *>(png.data()), fileSize);
if (png.size() > std::numeric_limits<int>::max()) {
// REPORT_ERROR
progressia::main::logging::fatal()
<< "Could not load \"" << path << "\": image file too large";
exit(1);
}
file.close();
int dataSize = static_cast<int>(png.size());
int width = 0;
int height = 0;
int channelsInFile = 0;
int width;
int height;
int channelsInFile;
Image::Byte *stbAllocatedData = stbi_load_from_memory(
png.data(), dataSize, &width, &height, &channelsInFile, STBI_rgb_alpha);
Image::Byte *stbAllocatedData =
stbi_load_from_memory(png.data(), png.size(), &width, &height,
&channelsInFile, STBI_rgb_alpha);
if (stbAllocatedData == NULL) {
if (stbAllocatedData == nullptr) {
fatal() << "Could not decode a PNG image from file " << path;
// REPORT_ERROR
exit(1);
@ -61,5 +66,4 @@ Image loadImage(const std::filesystem::path &path) {
data};
}
} // namespace main
} // namespace progressia
} // namespace progressia::main

View File

@ -3,8 +3,7 @@
#include <filesystem>
#include <vector>
namespace progressia {
namespace main {
namespace progressia::main {
class Image {
public:
@ -19,7 +18,6 @@ class Image {
Byte *getData();
};
Image loadImage(const std::filesystem::path &);
Image loadImage(const std::string &);
} // namespace main
} // namespace progressia
} // namespace progressia::main

View File

@ -28,3 +28,25 @@
}; \
}
// clang-format on
// clang-format off
#define DISABLE_MOVING(CLASS) \
CLASS &operator=(CLASS &&) = delete; \
CLASS(CLASS &&) = delete; \
// clang-format on
// clang-format off
#define DISABLE_COPYING(CLASS) \
CLASS &operator=(const CLASS &) = delete; \
CLASS(const CLASS &) = delete; \
// clang-format on
namespace progressia::main {
struct NonCopyable {
NonCopyable &operator=(const NonCopyable &) = delete;
NonCopyable(const NonCopyable &) = delete;
NonCopyable() = default;
};
} // namespace progressia::main

View File

@ -1,66 +0,0 @@
#!/bin/false
# Writes a message to stderr.
# Parameters:
# $@ - the message to display
function error() {
echo >&2 "`basename "$0"`: $@"
}
# Writes a message to stderr and exits with code 1.
# Parameters:
# $@ - the message to display
function fail() {
error "$@"
exit 1;
}
# Ensures that a variable with name $1 has a valid executable. If it does not,
# this function attempts to find an executable with a name suggested in $2...$n.
# In either way, if the variable does not end up naming an executable, fail() is
# called.
# Parameters:
# $1 - name of the variable to check and modify
# $2...$n - suggested executables (at least one)
# $FAIL_SILENTLY - if set, don't call exit and don't print anything on failure
function find_cmd() {
declare -n target="$1"
if [ -z "${target+x}" ]; then
for candidate in "${@:2}"; do
if command -v "$candidate" >/dev/null; then
target="$candidate"
break
fi
done
fi
if ! command -v "$target" >/dev/null; then
[ -n "${FAIL_SILENTLY+x}" ] && return 1
fail "Command $2 is not available. Check \$PATH or set \$$1."
fi
unset -n target
return 0
}
# Displays the command and then runs it.
# Parameters:
# $@ - the command to run
function echo_and_run() {
echo " > $*"
command "$@"
}
root_dir="$(dirname "$(dirname "$(realpath "${BASH_SOURCE[0]}")")")"
source_dir="$root_dir"
build_dir="$root_dir/build"
tools_dir="$root_dir/tools"
# Load private.sh
private_sh="$tools_dir/private.sh"
if [ -f "$private_sh" ]; then
[ -x "$private_sh" ] \
|| fail 'tools/private.sh exists but it is not executable'
source "$private_sh"
fi

View File

@ -1,199 +0,0 @@
#!/bin/bash
usage=\
"Usage: build.sh [OPTIONS...]
Build and run the game.
Options:
--debug make a debug build (default)
--release make a release build
--build-id=ID set the build ID. Default is dev.
--cmake-gen=ARGS pass additional arguments to pass to cmake when
generating build files. ARGS is the ;-separated list.
--dont-generate don't generate build instructions; use existing
configuration if building
--dont-build don't build; run existing binaries or generate build
instructions only
--debug-vulkan enable Vulkan validation layers from LunarG
-R, --run run the game after building
--memcheck[=ARGS] run the game using valgrind's memcheck dynamic memory
analysis tool. Implies -R. ARGS is the ;-separated
list of arguments to pass to valgrind/memcheck.
-h, --help display this help and exit
Environment variables:
PARALLELISM threads to use, default is 1
CMAKE cmake executable
VALGRIND valgrind executable
private.sh variables:
private_cmake_gen_args array of additional arguments to pass to cmake when
generating build files
See also: tools/cppcheck/use-cppcheck.sh --help
tools/clang-format/use-clang-format.sh --help
tools/setup.sh --help"
rsrc="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
source "$rsrc/bashlib.sh"
# Parse arguments
build_type=Debug
do_generate=true
cmake_gen_args=()
do_build=true
run_type=Normal
do_run=''
debug_vulkan=''
memcheck_args=()
for arg in "$@"; do
case "$arg" in
-h | --help )
echo "$usage"
exit
;;
--debug )
build_type=Debug
;;
--release )
build_type=Release
;;
--build-id )
fail "Option --build-id=ID requires a parameter"
;;
--build-id=* )
build_id="${arg#*=}"
;;
--cmake-gen )
fail "Option --cmake-gen=ARGS requires a parameter"
;;
--cmake-gen=* )
readarray -t -d ';' new_cmake_gen_args <<<"${arg#*=};"
unset new_cmake_gen_args[-1]
cmake_gen_args+=("${new_cmake_gen_args[@]}")
unset new_cmake_gen_args
;;
--debug-vulkan )
debug_vulkan=true
;;
-R | --run )
do_run=true
;;
--memcheck )
do_run=true
run_type=memcheck
;;
--memcheck=* )
do_run=true
run_type=memcheck
readarray -t -d ';' new_memcheck_args <<<"${arg#*=};"
unset new_memcheck_args[-1]
memcheck_args+=("${new_memcheck_args[@]}")
unset new_memcheck_args
;;
--dont-generate )
do_generate=''
;;
--dont-build )
do_build=''
;;
* )
fail "Unknown option '$arg'"
;;
esac
done
if [ -z "$do_build" -a -z "$do_generate" -a ${#cmake_gen_args[@]} != 0 ]; then
fail "CMake arguments are set, but no build is requested. Aborting"
fi
if [ -z "$do_build" -a -z "$do_generate" -a -z "$do_run" ]; then
fail "Nothing to do"
fi
# Generate build files
find_cmd CMAKE cmake
if [ $do_generate ]; then
cmake_gen_managed_args=(
-DCMAKE_BUILD_TYPE=$build_type
-DVULKAN_ERROR_CHECKING=`[ $debug_vulkan ] && echo ON || echo OFF`
-UBUILD_ID
)
[ -n "${build_id+x}" ] && cmake_gen_managed_args+=(
-DBUILD_ID="$build_id"
)
echo_and_run "$CMAKE" \
-B "$build_dir" \
-S "$source_dir" \
"${cmake_gen_managed_args[@]}" \
"${private_cmake_gen_args[@]}" \
"${cmake_gen_args[@]}" \
|| fail "Could not generate build files"
fi
# Build
find_cmd CMAKE cmake
if [ $do_build ]; then
options=()
[ -n "${PARALLELISM+x}" ] && options+=(-j "$PARALLELISM")
echo_and_run "$CMAKE" \
--build "$build_dir" \
"${options[@]}" \
|| fail "Build failed"
unset options
fi
# Run
if [ $do_run ]; then
run_command=()
if [ $run_type == memcheck ]; then
find_cmd VALGRIND valgrind
run_command+=(
"$VALGRIND"
--tool=memcheck
--suppressions="$tools_dir"/memcheck/suppressions.supp
"${memcheck_args[@]}"
--
)
fi
run_command+=(
"$build_dir/progressia"
)
run_dir="$root_dir/run"
mkdir -p "$run_dir"
(
cd "$run_dir"
echo_and_run "${run_command[@]}"
echo "Process exited with code $?"
)
fi

View File

@ -1,4 +0,0 @@
BasedOnStyle: LLVM
# Use larger indentation
IndentWidth: 4

View File

@ -1,102 +0,0 @@
#!/bin/bash
usage=\
"Usage: use-clang-format.sh git
or: use-clang-format.sh files FILES...
or: use-clang-format.sh raw ARGUMENTS...
In the 1st form, format all files that have changed since last git commit.
In the 2nd form, format all FILES, treating directories recursively.
In the 3rd form, run \`clang-format --style=<style> ARGUMENTS...\`.
Environment variables:
CLANG_FORMAT clang-format executable
CLANG_FORMAT_DIFF clang-format-diff script"
rsrc="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
source "$rsrc/../bashlib.sh"
case "$1" in
git )
find_cmd CLANG_FORMAT_DIFF \
clang-format-diff-13 \
clang-format-diff \
clang-format-diff.py
;;
files | raw )
find_cmd CLANG_FORMAT \
clang-format-13 \
clang-format
;;
-h | --help | '' )
echo "$usage"
exit
;;
* )
fail "Unknown option '$1'"
;;
esac
# Generate style argument
style=''
while IFS='' read line; do
[ -z "$line" ] && continue
[ "${line:0:1}" = '#' ] && continue
[ -n "$style" ] && style+=', '
style+="$line"
done < "$rsrc/clang-format.yml"
style="{$style}" # Not typo
case "$1" in
git )
unstaged_changes="`git diff --name-only`"
if [ -n "$unstaged_changes" ]; then
fail "Refusing to operate in git repository with unstaged changes:
$unstaged_changes"
fi
git diff -U0 --no-color --relative HEAD \
{desktop,main}/{'*.cpp','*.h','*.inl'} \
| command "$CLANG_FORMAT_DIFF" -p1 -style="$style" -i --verbose
exit_code="$?"
git add "$root_dir"
exit "$exit_code"
;;
raw )
command "$CLANG_FORMAT" -style="$style" "$@"
;;
files )
files=()
for input in "${@:2}"; do
if [ -d "$input" ]; then
readarray -d '' current_files < <(
find "$input" \
\( -name '*.cpp' -o -name '*.h' -o -name '*.inl' \) \
-type f \
-print0 \
)
[ "${#current_files[@]}" -eq 0 ] \
&& fail "No suitable files found in directory $input"
files+=("${current_files[@]}")
else
case "$input" in
*.cpp | *.h | *.inl )
files+=("$input")
;;
* )
error "Refusing to format file '$input': `
`only .cpp, .h and .inl supported"
;;
esac
fi
done
[ "${#files[@]}" -eq 0 ] && fail "No files to format"
command "$CLANG_FORMAT" -style="$style" -i --verbose "${files[@]}"
;;
esac

View File

@ -1,56 +0,0 @@
# Global variables. Yikes. FIXME
set(tools ${PROJECT_SOURCE_DIR}/tools)
set(generated ${PROJECT_BINARY_DIR}/generated)
set(assets_to_embed "")
set(assets_to_embed_args "")
file(MAKE_DIRECTORY ${generated})
find_package(Vulkan COMPONENTS glslc REQUIRED)
find_program(glslc_executable NAMES glslc HINTS Vulkan::glslc)
set(shaders ${generated}/shaders)
file(MAKE_DIRECTORY ${shaders})
# Shedules compilation of shaders
# Adapted from https://stackoverflow.com/a/60472877/4463352
macro(compile_shader)
foreach(source ${ARGV})
get_filename_component(source_basename ${source} NAME)
set(tmp "${shaders}/${source_basename}.spv")
add_custom_command(
OUTPUT ${tmp}
DEPENDS ${source}
COMMAND ${glslc_executable}
-o ${tmp}
${CMAKE_CURRENT_SOURCE_DIR}/${source}
COMMENT "Compiling shader ${source}"
)
list(APPEND assets_to_embed_args "${tmp};as;${source_basename}.spv")
list(APPEND assets_to_embed "${tmp}")
unset(tmp)
unset(source_basename)
endforeach()
endmacro()
compile_shader(
desktop/graphics/shaders/shader.frag
desktop/graphics/shaders/shader.vert
)
# Generate embed files
add_custom_command(
OUTPUT ${generated}/embedded_resources.cpp
${generated}/embedded_resources.h
COMMAND ${tools}/embed/embed.py
--cpp ${generated}/embedded_resources.cpp
--header ${generated}/embedded_resources.h
--
${assets_to_embed_args}
DEPENDS "${assets_to_embed}"
${tools}/embed/embed.py
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
COMMENT "Embedding assets"
)

View File

@ -1,28 +0,0 @@
# CppCheck command line arguments
# Each line is treated as one argument, unless it is empty or it starts with #.
#
# Available variables:
# ${CMAKE_SOURCE_DIR} project root
# ${CMAKE_BINARY_DIR} CMake build directory
--enable=warning,style,information
#--enable=unusedFunction # Unused functions are often OK since they are intended
# # to be used later
#--enable=missingInclude # Very prone to false positives; system-dependent
--inconclusive
# SUPPRESSIONS
# Warnings that are suppressed on a case-by-case basis should be suppressed
# using inline suppressions.
# Warnings that were decided to be generally inapplicable should be suppressed
# using suppressions.txt.
# Warnings that result from the way cppcheck is invoked should be suppressed
# using this file.
--inline-suppr
--suppressions-list=${CMAKE_SOURCE_DIR}/tools/cppcheck/suppressions.txt
# N.B.: this path is also mentioned in use scripts
--cppcheck-build-dir=${CMAKE_BINARY_DIR}/cppcheck
--error-exitcode=2

View File

@ -1,18 +0,0 @@
# CppCheck global suppressions
# Do not use this file for suppressions that could easily be declared inline.
# Allow the use of implicit constructors.
noExplicitConstructor:*
# In most cases using STL algorithm functions causes unnecessary code bloat.
useStlAlgorithm:*
# cppcheck trips on #include <embedded_resources.h> and there's no way to
# suppress that exlusively
missingInclude:*
# Shut up. Just shut up.
unmatchedSuppression:*
# Do not check third-party libraries
*:*lib*

View File

@ -1,65 +0,0 @@
#!/bin/bash
usage=\
"Usage: use-cppcheck.sh
Run cppcheck with correct options.
Environment variables:
PARALLELISM threads to use, default is 1
CPPCHECK cppcheck executable
CMAKE cmake executable"
rsrc="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
source "$rsrc/../bashlib.sh"
find_cmd CPPCHECK cppcheck
find_cmd CMAKE cmake
case "$1" in
-h | --help )
echo "$usage"
exit
;;
esac
# Generate compile database for CppCheck
command "$CMAKE" \
-B "$build_dir" \
-S "$source_dir" \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
compile_database="$build_dir/compile_commands.json"
mkdir -p "$build_dir/cppcheck"
options=()
while IFS='' read -r line; do
[ -z "$line" ] && continue
[ "${line:0:1}" = '#' ] && continue
option="$(
CMAKE_SOURCE_DIR="$source_dir" \
CMAKE_BINARY_DIR="$build_dir" \
envsubst <<<"$line"
)"
options+=("$option")
done < "$tools_dir/cppcheck/options.txt"
[ -n "${PARALLELISM+x}" ] && options+=(-j "$PARALLELISM")
errors="`
echo_and_run "$CPPCHECK" \
--project="$compile_database" \
-D__CPPCHECK__ \
"${options[@]}" \
2>&1 >/dev/fd/0 # Store stderr into variable, pass stdout to our stdout
`"
exit_code="$?"
if [ "$exit_code" -eq 2 ]; then
less - <<<"$errors"
exit "$exit_code"
fi

57
tools/dev-mode.cmake Normal file
View File

@ -0,0 +1,57 @@
if (DEV_MODE)
find_program(clang_tidy_EXECUTABLE NAMES clang-tidy-13 clang-tidy REQUIRED)
find_package(Python3 COMPONENTS Interpreter REQUIRED)
# Setup clang-tidy
list(APPEND clang_tidy_command "${clang_tidy_EXECUTABLE}"
"--warnings-as-errors=*"
"--use-color")
set_target_properties(progressia
PROPERTIES CXX_CLANG_TIDY "${clang_tidy_command}")
# Display the marker for pre-commit.py at build time
add_custom_target(clang_tidy_marker ALL
COMMAND ${CMAKE_COMMAND} -E echo
"Clang-tidy is enabled. This is a marker for pre-commit.py")
# Notify pre-commit.py about CMake settings
execute_process(COMMAND ${Python3_EXECUTABLE} ${tools}/pre-commit.py
set-build-info -- "${CMAKE_COMMAND}" "${CMAKE_BINARY_DIR}"
RESULT_VARIABLE set_build_info_RESULT)
if(${set_build_info_RESULT})
message(FATAL_ERROR "pre-commit.py set-build-info failed")
endif()
# Setup pre-commit git hook
if (IS_DIRECTORY "${CMAKE_SOURCE_DIR}/.git/hooks")
set(pre_commit_hook "${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit")
if (NOT EXISTS "${pre_commit_hook}")
file(WRITE "${pre_commit_hook}"
"#!/bin/sh\n"
"# Progressia autogenerated pre-commit hook\n"
"# You may modify this hook freely "
"(just make sure the checks run)\n"
"/bin/env python3 ${CMAKE_SOURCE_DIR}/tools/pre-commit.py run")
if (${CMAKE_VERSION} VERSION_LESS "3.19.0")
if (${CMAKE_HOST_UNIX})
execute_process(COMMAND chmod "755" "${pre_commit_hook}"
RESULT_VARIABLE chmod_RESULT)
if (${chmod_RESULT})
message(FATAL_ERROR "Could not make git pre-commit hook executable")
endif()
endif()
else()
file(CHMOD "${pre_commit_hook}"
PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE)
endif()
endif()
unset(pre_commit_hook)
endif()
endif()

89
tools/embed/embed.cmake Normal file
View File

@ -0,0 +1,89 @@
# embed.cmake
# Generates embedded_resources.h and embedded_resources.cpp
find_package(Python3 COMPONENTS Interpreter REQUIRED)
macro (get_target_property_or var target prop default)
get_property(__is_set TARGET ${target} PROPERTY ${prop} SET)
if (__is_set)
get_property(${var} TARGET ${target} PROPERTY ${prop})
else()
set(${var} "${default}")
endif()
unset(__is_set)
endmacro()
function (target_embeds)
set(expecting_name FALSE)
set(target "")
set(current_asset "")
foreach (word ${ARGV})
# First argument is target name
if (target STREQUAL "")
set(target "${word}")
get_target_property_or(script_args "${target}" EMBED_ARGS "")
get_target_property_or(embeds "${target}" EMBEDS "")
continue()
endif()
if (current_asset STREQUAL "")
# Beginning of asset declaration (1/2)
set(current_asset "${word}")
elseif (expecting_name)
# End of "asset AS asset_name"
list(APPEND script_args "${current_asset};as;${word}")
list(APPEND embeds ${current_asset})
set(current_asset "")
set(expecting_name FALSE)
elseif ("${word}" STREQUAL "AS")
# Keyword AS in "asset AS asset_name"
set(expecting_name TRUE)
else()
# End of asset without AS, beginning of asset declaration (2/2)
list(APPEND script_args "${current_asset};as;${current_asset}")
list(APPEND embeds ${current_asset})
set(current_asset "${word}")
endif()
endforeach()
if (expecting_name)
message(FATAL_ERROR "No name given for asset \"${current_asset}\"")
endif()
if (NOT current_asset STREQUAL "")
list(APPEND script_args "${current_asset};as;${current_asset}")
endif()
set_target_properties("${target}" PROPERTIES EMBED_ARGS "${script_args}")
set_target_properties("${target}" PROPERTIES EMBEDS "${embeds}")
endfunction()
file(MAKE_DIRECTORY "${generated}/embedded_resources")
function(compile_embeds target)
get_target_property(script_args "${target}" EMBED_ARGS)
get_target_property(embeds "${target}" EMBEDS)
add_custom_command(
OUTPUT ${generated}/embedded_resources/embedded_resources.cpp
${generated}/embedded_resources/embedded_resources.h
COMMAND ${Python3_EXECUTABLE} ${tools}/embed/embed.py
--cpp ${generated}/embedded_resources/embedded_resources.cpp
--header ${generated}/embedded_resources/embedded_resources.h
--
${script_args}
DEPENDS ${embeds}
${tools}/embed/embed.py
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
COMMENT "Embedding assets"
)
endfunction()

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3
usage = \
'''Usage: embed.py --cpp OUT_CPP --header OUT_H [--] [INPUT as PATH]...
'''Usage: %(me)s --cpp OUT_CPP --header OUT_H [--] [INPUT as PATH]...
Generate C++ source code that includes binary contents of INPUT files.
Each file in INPUT is stored as a resource: a static array of unsigned char.
@ -79,6 +79,7 @@ def main():
fail(f"Unknown option '{arg}'")
elif considerOptions and (arg == '-h' or arg == '--help'):
print(usage % {'me': os.path.basename(sys.argv[0])})
sys.exit(0)
elif considerOptions and arg == '--':
@ -237,8 +238,8 @@ namespace {
mid=\
'''
std::unordered_map<std::string,
__embedded_resources::EmbeddedResource>
const std::unordered_map<std::string,
__embedded_resources::EmbeddedResource>
EMBEDDED_RESOURCES =
{
''',

View File

@ -1,51 +0,0 @@
#!/bin/bash
me="$(realpath "${BASH_SOURCE[0]}")"
if [ "$(basename "$me")" = 'pre-commit' ]; then
# i write good shell scripts - Javapony 2022-10-07
root_dir="$(realpath "$(dirname "$me")/../../")"
hook_source="$root_dir/tools/git/hook_pre_commit.sh"
if [ "$hook_source" -nt "$me" ]; then
if [ -n "${ALREADY_UPDATED+x}" ]; then
echo >&2 "git pre-commit hook: Attempted recursive hook update. `
`Something is very wrong."
exit 1
fi
echo ''
echo "===== tools/git/hook_pre_commit.sh updated; `
`replacing pre-commit hook ====="
echo ''
cp "$hook_source" "$me" &&
chmod +x "$me" \
|| fail 'Update failed'
ALREADY_UPDATED=true "$me"
exit $?
fi
source "$root_dir/tools/bashlib.sh"
else
rsrc="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
source "$rsrc/../bashlib.sh"
fi
unstaged_changes="`git diff --name-only`"
if [ -n "$unstaged_changes" ]; then
fail "Please stage all stash all unstaged changes in the following files:
$unstaged_changes"
fi
echo_and_run "$tools_dir/cppcheck/use-cppcheck.sh" \
|| fail "Cppcheck has generated warnings, aborting commit"
echo_and_run "$tools_dir/clang-format/use-clang-format.sh" git \
|| fail "clang-format has failed, aborting commit"
echo_and_run "$tools_dir/build.sh" --dont-generate \
|| fail "Could not build project, aborting commit"
echo 'All checks passed'

53
tools/glslc.cmake Normal file
View File

@ -0,0 +1,53 @@
# glslc.cmake
# Compiles GLSL shaders to SPV files
find_package(Vulkan COMPONENTS glslc REQUIRED)
find_program(glslc_EXECUTABLE NAMES glslc HINTS Vulkan::glslc REQUIRED)
macro (get_target_property_or var target prop default)
get_property(__is_set TARGET ${target} PROPERTY ${prop} SET)
if (__is_set)
get_property(${var} TARGET ${target} PROPERTY ${prop})
else()
set(${var} "${default}")
endif()
unset(__is_set)
endmacro()
function (target_glsl_shaders)
set(target "")
foreach (word ${ARGV})
# First argument is target name
if (target STREQUAL "")
set(target ${word})
get_target_property_or(glsl_shaders ${target} GLSL_SHADERS "")
else()
list(APPEND glsl_shaders ${word})
endif()
endforeach()
set_target_properties(${target} PROPERTIES GLSL_SHADERS "${glsl_shaders}")
endfunction()
file(MAKE_DIRECTORY "${generated}/compiled_glsl_shaders")
function(compile_glsl target)
get_target_property(glsl_shaders ${target} GLSL_SHADERS)
foreach (source_path ${glsl_shaders})
get_filename_component(source_basename ${source_path} NAME)
set(spv_path
"${generated}/compiled_glsl_shaders/${source_basename}.spv")
add_custom_command(
OUTPUT ${spv_path}
DEPENDS ${source_path}
COMMAND ${glslc_EXECUTABLE}
-o ${spv_path}
${CMAKE_CURRENT_SOURCE_DIR}/${source_path}
COMMENT "Compiling shader ${source_path}"
)
target_embeds(${target} ${spv_path} AS "${source_basename}.spv")
endforeach()
endfunction()

416
tools/pre-commit.py Executable file
View File

@ -0,0 +1,416 @@
#!/usr/bin/env python3
usage = \
'''Usage: %(me)s run [OPTIONS...]
or: %(me)s restore [OPTIONS...]
or: %(me)s set-build-info CMAKE_EXECUTABLE CMAKE_BINARY_DIR
In the 1st form, run standard pre-commit procedure for Progressia.
In the 2nd form, attempt to restore workspace if the pre-commit hook failed.
In the 3rd form, update cached build settings.
--dry-run do not change anything in git or in the filesystem;
implies --verbose
--verbose print commands and diagnostics
--help display this help and exit
--version display version information and exit
Currently, the pre-commit procedure performs the following:
1. format staged changes
2. attempt to compile with staged changes only
pre-commit-settings.json values:
build-root CMake binary dir to use (filled in by CMake)
parallelism threads to use, default is 1
git git command, default is null
cmake cmake command, default is null (filled in by CMake)
clang-format-diff clang-format-diff command, default is null
Use semicolons to separate arguments in git, cmake and clang-format-diff'''
# Script version. Increment when script logic changes significantly.
# Commit change separately.
version = 1
# Source directories to format
src_dirs = ['desktop', 'main']
# File extensions to format
exts = ['cpp', 'h', 'inl']
import sys
import os
import subprocess
import shutil
import json
STASH_NAME = 'progressia_pre_commit_stash'
# Paths are relative to this script's directory, tools/
SETTINGS_PATH = 'pre-commit-settings.json'
CLANG_TIDY_CHECK_MARKER = 'Clang-tidy is enabled. ' \
'This is a marker for pre-commit.py'
def fail(*args, code=1):
"""Print an error message and exit with given code (default 1)"""
print(my_name + ':', *args, file=sys.stderr)
sys.exit(1)
def verbose(*args):
"""Print a message in verbose mode only."""
if verbose_mode:
print(my_name + ':', *args)
def long_print_iter(title, it):
"""Print contents of iterable titled as specified. If iterable is empty,
print the string (nothing) instead.
"""
print(title + ':')
if len(it) > 0:
print('\t' + '\n\t'.join(it) + '\n')
else:
print('\t(nothing)\n')
def invoke(*cmd, result_when_dry=None, quiet=True, text=True, stdin=None):
"""Execute given system command and return its stdout. If command fails,
throw CalledProcessError.
When in verbose mode, log command before execution. If in dry-run mode and
result_when_dry is not None, skip execution and return result_when_dry
instead.
Keyword arguments:
result_when_dry -- unless None (default), skip execution and return this
quiet -- if False, print stdout (default True)
text -- treat stdin and stdout as text rather than bytes (default False)
stdin -- unless None (default), send this to stdin of spawned process
"""
verbose('command', *(repr(c) for c in cmd))
if dry_run and result_when_dry is not None:
print(my_name + ': skipped: --dry-run')
return result_when_dry
popen = subprocess.Popen(cmd,
stdout=subprocess.PIPE,
text=text,
universal_newlines=text,
stdin=subprocess.PIPE if stdin else subprocess.DEVNULL)
stdout, _ = popen.communicate(input=stdin)
if text and not quiet:
print(stdout, end='')
return_code = popen.wait()
if return_code != 0:
raise subprocess.CalledProcessError(return_code, cmd)
return stdout
def get_file_sets():
"""Return sets of indexed and unindexed files according to Git"""
def git_z(*cmd):
raw = invoke(*git, *cmd, '-z', text=False)
return set(f.decode() for f in raw.split(b'\x00') if len(f) != 0)
indexed = git_z('diff', '--name-only', '--cached')
unindexed = git_z('diff', '--name-only') | \
git_z('ls-files', '--other', '--exclude-standard')
return indexed, unindexed
def run_safety_checks(indexed, unindexed):
if invoke(*git, 'stash', 'list', '--grep', f"\\b{STASH_NAME}$") != '':
fail(f"Cannot run pre-commit checks: stash {STASH_NAME} exists. "
f"Use `{my_name} restore` to restore workspace and repository "
f"state")
both_changes = indexed & unindexed
if len(both_changes) != 0:
fail(f"Cannot run pre-commit checks: files with indexed and unindexed "
'changes exist:\n\n\t' +
'\n\t'.join(both_changes) +
'\n')
def do_restore():
"""Restore repository and filesystem. Fail if stash not found."""
print('Redoing rolled back changes')
git_list = invoke(*git, 'stash', 'list', '--grep', f"\\b{STASH_NAME}$")
if len(git_list) == 0:
if dry_run:
stash_name = 'stash@{0}'
else:
fail(f"Cannot restore repository: stash {STASH_NAME} not found")
else:
stash_name, _, _ = git_list.partition(':')
invoke(*git, 'stash', 'pop', '--index', '--quiet', stash_name,
result_when_dry='', quiet=False)
def format_project():
"""Format staged files with clang-format-diff."""
diff = invoke(*git, 'diff', '-U0', '--no-color', '--relative', 'HEAD',
*(f"{d}/*.{e}" for d in src_dirs for e in exts))
invoke(*clang_format_diff, '-p1', '-i', '--verbose',
stdin=diff, result_when_dry='', quiet=False)
def unformat_project(indexed_existing):
"""Undo formatting changes introduced by format_project()."""
print('Undoing formatting changes')
if len(indexed_existing) == 0:
print('Nothing to do: all indexed changes are deletions')
return
invoke(*git, 'restore', '--', *indexed_existing)
def build_project():
"""Build project with cmake."""
print('Building project')
build_log = invoke(*cmake,
'--build', build_root,
'--parallel', str(parallelism),
result_when_dry=CLANG_TIDY_CHECK_MARKER,
quiet=False)
if CLANG_TIDY_CHECK_MARKER not in build_log.splitlines():
fail('Project build was successful, but clang-tidy did not run. '
'Please make sure DEV_MODE is ON and regenerate CMake cache.')
print('Success')
def pre_commit():
"""Run pre-commit checks."""
if build_root is None:
fail(f"build-root is not set in {SETTINGS_PATH}. Compile project "
'manually to set this variable properly.')
if not os.path.exists(build_root):
fail(f"build-root {build_root} does not exist. Compile project "
'manually to set this variable properly.')
cmakeCache = os.path.join(build_root, 'CMakeCache.txt')
if not os.path.exists(cmakeCache):
fail(f"{cmakeCache} does not exist. build-root is likely invalid. "
'Compile project manually to set this variable properly.')
indexed, unindexed = get_file_sets()
indexed_existing = [f for f in indexed if os.path.exists(f)]
if verbose_mode:
long_print_iter('Indexed changes', indexed)
long_print_iter('Unindexed changes', unindexed)
long_print_iter('Indexed changes without deletions', indexed_existing)
if len(indexed) == 0:
fail('No indexed changes. You probably forgot to run `git add .`')
run_safety_checks(indexed, unindexed)
undo_formatting = False
restore = False
try:
if len(unindexed) != 0:
long_print_iter('Unindexed changes found in files', unindexed)
print('These changes will be rolled back temporarily')
invoke(*git, 'stash', 'push',
'--keep-index',
'--include-untracked',
'--message', STASH_NAME,
result_when_dry='', quiet=False)
restore = True
format_project()
undo_formatting = True
build_project()
undo_formatting = False
finally:
if undo_formatting:
unformat_project(indexed_existing)
if restore:
do_restore()
print('Staging formatting changes')
if len(indexed_existing) == 0:
print('Nothing to do: all indexed changes are deletions')
else:
invoke(*git, 'add', '--', *indexed_existing,
result_when_dry='', quiet=False)
def get_settings_path():
return os.path.abspath(os.path.join(os.path.dirname(__file__),
SETTINGS_PATH))
def save_settings():
"""Save tools/pre-commit-settings.json."""
path = get_settings_path()
verbose(f"Saving settings into {path}")
if not dry_run:
with open(path, mode='w') as f:
json.dump(settings, f, indent=4)
else:
verbose(' skipped: --dry-run')
def set_build_info():
"""Set build info in tools/pre-commit-settings.json."""
settings['build_root'] = arg_build_root
settings['cmake'] = arg_cmake_executable
save_settings()
def parse_args():
"""Parse sys.argv and environment variables; set corresponding globals.
Return (action, arguments for set-build-root).
"""
global action
global verbose_mode
global dry_run
global allow_update
consider_options = True
action = None
arg_cmake_executable = None
arg_build_root = None
for arg in sys.argv[1:]:
if arg == 'restore' or arg == 'set-build-info' or arg == 'run':
if action is not None:
fail(f"Cannot use '{arg}' and '{action}' together")
action = arg
elif consider_options and arg.startswith('-'):
if arg == '-h' or arg == '--help' or arg == 'help' or arg == '/?':
print(usage % {'me': my_name})
sys.exit(0)
elif arg == '--version':
print(f"Progressia pre-commit script, version {version}")
sys.exit(0)
elif arg == '--verbose':
verbose_mode = True
elif arg == '--dry-run':
dry_run = True
verbose_mode = True
elif arg == '--':
consider_options = False
else:
fail(f"Unknown option '{arg}'")
elif action == 'set-build-info' and arg_cmake_executable is None:
arg_cmake_executable = arg
elif action == 'set-build-info' and arg_build_root is None:
arg_build_root = arg
else:
fail(f"Unknown or unexpected argument '{arg}'")
if action is None:
fail('No action specified')
if action == 'set-build-info' and arg_cmake_executable is None:
fail('No CMake executable given')
if action == 'set-build-info' and arg_build_root is None:
fail('No build root given')
return action, arg_build_root, arg_cmake_executable
def load_settings():
"""Ensure pre-commit-settings.json exists and is loaded into memory."""
global settings
path = get_settings_path()
if os.path.exists(path):
with open(path, mode='r') as f:
settings = json.load(f)
else:
verbose(f"{path} not found, using defaults")
settings = {
"__comment": "See `pre-commit.py --help` for documentation",
"build_root": None,
"git": None,
"cmake": None,
"clang_format_diff": None,
"parallelism": 1
}
save_settings()
def parse_settings():
"""Load values from settings and check their validity."""
global settings
global build_root
global git
global cmake
global clang_format_diff
global parallelism
build_root = settings['build_root']
parallelism = settings['parallelism']
def find_command(hints, settings_name):
if settings[settings_name] is not None:
hints = [settings[settings_name]]
cmds = (hint.split(';') for hint in hints)
res = next((cmd for cmd in cmds if shutil.which(cmd[0])), None) \
or fail(f"Command {hints[0]} not found. Set {settings_name} " +
f"in {path} or check PATH")
verbose(f"Found command {hints[0]}:", *(repr(c) for c in res))
return res
git = find_command(['git'], 'git')
cmake = find_command(['cmake'], 'cmake')
clang_format_diff = find_command(['clang-format-diff-13',
'clang-format-diff',
'clang-format-diff.py'],
'clang_format_diff')
if __name__ == '__main__':
my_name = os.path.basename(sys.argv[0])
verbose_mode = False
dry_run = False
allow_update = True
action, arg_build_root, arg_cmake_executable = parse_args()
load_settings()
if dry_run:
print('Running in dry mode: no changes to filesystem or git will '
'actually be performed')
try:
if action == 'set-build-info':
set_build_info()
elif action == 'restore':
parse_settings()
do_restore()
indexed, unindexed = get_file_sets()
if indexed & unindexed:
unformat_project(indexed)
else:
parse_settings()
pre_commit()
except subprocess.CalledProcessError as e:
fail('Command', *(repr(c) for c in e.cmd),
f"exited with code {e.returncode}")

View File

@ -1,146 +0,0 @@
#!/bin/bash
usage=\
"Usage: setup.sh [--for-development]
Set up the development environment after \`git clone\`
Options:
--for-development perform additional setup only necessary for developers
-h, --help display this help and exit"
rsrc="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
source "$rsrc/bashlib.sh" || {
echo >&2 'Could not load bashlib'
exit 1
}
cd "$root_dir"
# Parse arguments
for_development=''
for arg in "$@"; do
case "$arg" in
-h | --help )
echo "$usage"
exit
;;
--for-development )
for_development=true
;;
* )
fail "Unknown option '$arg'"
;;
esac
done
# Сreate private.sh
if [ ! -e "$private_sh" ]; then
echo '#!/bin/bash
# This file is ignored by git. Use it to configure shell scripts in tools/
# for your development environment.
PARALLELISM=1
#PATH="$PATH:/opt/whatever"
' >"$private_sh" &&
chmod +x "$private_sh" ||
fail "tools/private.sh was not found; could not create it"
echo "Created tools/private.sh"
else
echo "Found and loaded private.sh"
fi
# Check available commands
failed=()
function check_cmd() {
if FAIL_SILENTLY=true find_cmd found "$@"; then
echo "Found command $found"
else
failed+=("command $1")
echo "Could not find command $1"
fi
unset found
}
check_cmd cmake
check_cmd python3
check_cmd glslc
if [ $for_development ]; then
check_cmd git
check_cmd cppcheck
check_cmd clang-format-13 clang-format
check_cmd clang-format-diff-13 clang-format-diff clang-format-diff.py
check_cmd valgrind
fi
# Try generating build files
if FAIL_SILENTLY=true find_cmd CMAKE cmake; then
if CMAKE="$CMAKE" "$tools_dir/build.sh" --dont-build; then
echo 'CMake did not encounter any problems'
else
echo 'Could not generate build files; libraries are probably missing'
failed+=('some libraries, probably (see CMake messages for details)')
fi
else
echo 'Skipping CMake test because cmake was not found'
fi
# Display accumulated errors
[ ${#failed[@]} -ne 0 ] &&
fail "Could not find the following required commands or libraries:
`for f in "${failed[@]}"; do echo " $f"; done`
You can resolve these errors in the following ways:
1. Install required software packages. See README for specific instructions.
2. Edit PATH or CMAKE_MODULE_PATH environment variables in tools/private.sh
to include your installation directories.
"
# Set executable flags
chmod -v +x tools/build.sh \
tools/embed/embed.py \
|| fail 'Could not make scripts executable'
if [ $for_development ]; then
chmod -v +x tools/clang-format/use-clang-format.sh \
tools/cppcheck/use-cppcheck.sh \
|| fail 'Could not make developer scripts executable'
fi
# Set git hook
if [ $for_development ]; then
mkdir -vp .git/hooks &&
cp -v tools/git/hook_pre_commit.sh .git/hooks/pre-commit &&
chmod -v +x .git/hooks/pre-commit \
|| fail 'Could not setup git pre-commit hook'
fi
echo 'Setup complete'