Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix file path resolution to be portable #20

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 5 additions & 15 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ option(USE_HIGH_PERFORMANCE_GPU "Use high performance GPU, most likely a discret
if (USE_HIGH_PERFORMANCE_GPU)
add_compile_definitions(WGPU_GPU_HIGH_PERFORMANCE="ON")
endif()
configure_file(resources/config.h.in config/config.h)

add_executable(Template
src/implementations.cpp
Expand All @@ -37,6 +38,7 @@ add_executable(Template
src/Camera.cpp
src/Colormap.h
src/Colormap.cpp
src/PathFinder.cpp
)

target_compile_definitions(Template PRIVATE
Expand Down Expand Up @@ -71,24 +73,12 @@ file(GLOB_RECURSE UTIL_HEADERS
)
target_sources(Template PRIVATE ${UTIL_SOURCES} ${UTIL_HEADERS})

if(DEV_MODE)
# In dev mode, we load resources from the source tree, so that when we
# dynamically edit resources (like shaders), these are correctly
# versionned.
target_compile_definitions(Template PRIVATE
RESOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/resources"
)
else()
# In release mode, we just load resources relatively to wherever the
# executable is launched from, so that the binary is portable
target_compile_definitions(Template PRIVATE
RESOURCE_DIR="./resources"
)
endif()

target_include_directories(Template PRIVATE .)
target_include_directories(Template PRIVATE src)
target_include_directories(Template PRIVATE thirdparty)
target_include_directories(Template PRIVATE ${CMAKE_BINARY_DIR})
# message(STATUS ${CMAKE_CURRENT_SOURCE_DIR})
# message(STATUS ${CMAKE_BINARY_DIR})

target_link_libraries(Template PRIVATE glfw webgpu glfw3webgpu imgui)

Expand Down
23 changes: 23 additions & 0 deletions guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -671,3 +671,26 @@ void Scene1::onDraw(Renderer& renderer){
```
Note that the colormap returns no alpha channel so we need to expand the returned color to be RGBA. Also note the division by 1.5, the expected maximum lifetime of a particle before deletion and needs to be kept in sync with other lifetime based parts of the code, which could be ensured by using a member of the Scene class to represent the maximum lifetime of a particle instead of hardcoding it.
![image](https://github.com/user-attachments/assets/37f5c448-ea09-4c0f-884b-21101b8f02dd)

## Loading files

Finding the correct file to open when adding interesting features can be troublesome in C++. This framework contains a small helper function that makes this a lot easier. The default options should be to add files to a specific resources folder (the template already contains one with shaders and colormaps) and to include this directory with the executable when sending it to someone else.

To now look for files in this directory you can simply
```cpp
#include "PathFinder.h"
//...

std::filesystem::path p = resolveFile("resources/yourFile.ending");
```

The resolveFile function will check four locations: (a) the current working directory (b) the directory where the executable is located (c) the source directory and (d) the build directory. Note that the last two options are not portable and will not work on other peoples' systems. To restrict the search to portable paths only you can use the portable option of resolveFile, e.g.,

```cpp
#include "PathFinder.h"
//...

std::filesystem::path p = resolveFile("resources/yourFile.ending",{},true);
```

The second argument here can also be used to provide additional manual search paths (which are also most likely not portable).
4 changes: 4 additions & 0 deletions resources/config.h.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#pragma once
#include <string>
std::string sourceDirectory = "@CMAKE_SOURCE_DIR@";
std::string buildDirectory = "@CMAKE_BINARY_DIR@";
9 changes: 3 additions & 6 deletions src/Colormap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@
#include <iostream>
#include <algorithm>

#ifndef RESOURCE_DIR
#define RESOURCE_DIR "this will be defined by cmake depending on the build type. This define is to disable error squiggles"
#endif

std::map<std::string, int> Colormap::indices;
std::vector<std::string> Colormap::names;
ResourceManager::Image Colormap::colormaps;
Expand Down Expand Up @@ -42,9 +38,10 @@ glm::vec3 Colormap::operator()(float value)
return color(value);
}

#include "PathFinder.h"
void Colormap::init()
{
std::filesystem::path path = RESOURCE_DIR "/colormaps.txt";
std::filesystem::path path = resolveFile("resources/colormaps.txt");
std::ifstream file(path);
if (!file.is_open())
{
Expand All @@ -62,6 +59,6 @@ void Colormap::init()
indices[line] = offset;
offset++;
}
path = RESOURCE_DIR "/colormaps.png";
path = resolveFile("resources/colormaps.png");
colormaps = ResourceManager::loadImage(path);
}
101 changes: 101 additions & 0 deletions src/PathFinder.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#include <sstream>
#include <iostream>
#include <filesystem>
#include "PathFinder.h"
#include <config/config.h>

std::filesystem::path expand(std::filesystem::path in) {
namespace fs = std::filesystem;
#ifndef _WIN32
if (in.string().size() < 1) return in;

const char * home = getenv("HOME");
if (home == NULL) {
std::cerr << "error: HOME variable not set." << std::endl;
throw std::invalid_argument("error: HOME environment variable not set.");
}

std::string s = in.string();
if (s[0] == '~') {
s = std::string(home) + s.substr(1, s.size() - 1);
return fs::path(s);
}
else {
return in;
}
#else
if (in.string().size() < 1) return in;

const char * home = getenv("USERPROFILE");
if (home == NULL) {
std::cerr << "error: USERPROFILE variable not set." << std::endl;
throw std::invalid_argument("error: USERPROFILE environment variable not set.");
}

std::string s = in.string();
if (s[0] == '~') {
s = std::string(home) + s.substr(1, s.size() - 1);
return fs::path(s);
}
else {
return in;
}
#endif

}

std::filesystem::path workingDirectory;
std::filesystem::path binaryDirectory;

std::filesystem::path resolveFile(std::string fileName, std::vector<std::string> search_paths, bool portable) {
namespace fs = std::filesystem;
fs::path working_dir = workingDirectory;
fs::path binary_dir = binaryDirectory;
fs::path source_dir = sourceDirectory;
fs::path build_dir = buildDirectory;

fs::path expanded = expand(fs::path(fileName));

fs::path base_path = "";
if (fs::exists(expand(fs::path(fileName))))
return expand(fs::path(fileName));
for (const auto& path : search_paths){
auto p = expand(fs::path(path));
if (fs::exists(p / fileName))
return p.string() + std::string("/") + fileName;
}

if (fs::exists(fileName)) return fs::path(fileName);
if (fs::exists(expanded))
return expanded;

for (const auto &pathi : search_paths) {
auto path = expand(fs::path(pathi));
if (fs::exists(working_dir / path / fileName))
return (working_dir / path / fileName).string();
if (fs::exists(binary_dir / path / fileName))
return (binary_dir / path / fileName).string();
if (!portable){
if (fs::exists(source_dir / path / fileName))
return (source_dir / path / fileName).string();
if (fs::exists(build_dir / path / fileName))
return (build_dir / path / fileName).string();
}
}

if (fs::exists(working_dir / fileName))
return (working_dir / fileName);
if (fs::exists(binary_dir / fileName))
return (binary_dir / fileName);
if(!portable){
if (fs::exists(source_dir / fileName))
return (source_dir / fileName);
if (fs::exists(build_dir / fileName))
return (build_dir / fileName);
}

std::stringstream sstream;
sstream << "File '" << fileName << "' could not be found in any provided search path" << std::endl;
std::cerr << sstream.str();
throw std::runtime_error(sstream.str().c_str());
}
11 changes: 11 additions & 0 deletions src/PathFinder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once
#include <sstream>
#include <filesystem>
#include <vector>
#include <string>

std::filesystem::path expand(std::filesystem::path in);
std::filesystem::path resolveFile(std::string fileName, std::vector<std::string> search_paths = {}, bool portable = false);

extern std::filesystem::path workingDirectory;
extern std::filesystem::path binaryDirectory;
4 changes: 0 additions & 4 deletions src/Renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@

using namespace wgpu;

#ifndef RESOURCE_DIR
#define RESOURCE_DIR "this will be defined by cmake depending on the build type. This define is to disable error squiggles"
#endif

Renderer::Renderer()
{
initWindowAndDevice();
Expand Down
7 changes: 6 additions & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
#include "Renderer.h"
#include "Simulator.h"
#include "PathFinder.h"

int main(int, char **)
int main(int argc, char ** argv)
{
namespace fs = std::filesystem;
workingDirectory = fs::absolute(fs::path(argv[0])).remove_filename();
binaryDirectory = fs::current_path();

Renderer renderer = Renderer();
Simulator simulator(renderer);

Expand Down
7 changes: 2 additions & 5 deletions src/pipelines/ImagePipeline.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
#include "ImagePipeline.h"
#include "Colormap.h"

#ifndef RESOURCE_DIR
#define RESOURCE_DIR "this will be defined by cmake depending on the build type. This define is to disable error squiggles"
#endif
#include "PathFinder.h"

using namespace wgpu;

Expand All @@ -12,7 +9,7 @@ bool ImagePipeline::init(Device &device, TextureFormat &swapChainFormat, Queue &
this->device = device;
this->queue = queue;
this->swapChainFormat = swapChainFormat;
shaderModule = ResourceManager::loadShaderModule(RESOURCE_DIR "/image_shader.wgsl", device);
shaderModule = ResourceManager::loadShaderModule(resolveFile("resources/image_shader.wgsl"), device);
RenderPipelineDescriptor pipelineDesc;
pipelineDesc.label = "Image pipeline";

Expand Down
7 changes: 2 additions & 5 deletions src/pipelines/InstancingPipeline.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
#include "InstancingPipeline.h"
#include "Renderer.h"
#include "PathFinder.h"
#include <numeric>
#include <algorithm>

#ifndef RESOURCE_DIR
#define RESOURCE_DIR "this will be defined by cmake depending on the build type. This define is to disable error squiggles"
#endif

using namespace wgpu;
using PrimitiveVertexAttributes = ResourceManager::PrimitiveVertexAttributes;
using InstancedVertexAttributes = ResourceManager::InstancedVertexAttributes;
Expand All @@ -17,7 +14,7 @@ void InstancingPipeline::init(Device &device, Queue &queue, TextureFormat &swapC
this->lightingUniforms = lightingUniforms;
this->device = device;
this->queue = queue;
shaderModule = ResourceManager::loadShaderModule(RESOURCE_DIR "/instancing_shader.wgsl", device);
shaderModule = ResourceManager::loadShaderModule(resolveFile("resources/instancing_shader.wgsl"), device);
RenderPipelineDescriptor pipelineDesc;

std::vector<VertexAttribute> lineVertexAttribs(2);
Expand Down
7 changes: 2 additions & 5 deletions src/pipelines/LinePipeline.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
#include "LinePipeline.h"
#include "Renderer.h"

#ifndef RESOURCE_DIR
#define RESOURCE_DIR "this will be defined by cmake depending on the build type. This define is to disable error squiggles"
#endif
#include "PathFinder.h"

using namespace wgpu;
using LineVertexAttributes = ResourceManager::LineVertexAttributes;
Expand All @@ -14,7 +11,7 @@ void LinePipeline::init(Device &device, Queue &queue, TextureFormat &swapChainFo
this->lightingUniforms = lightingUniforms;
this->device = device;
this->queue = queue;
shaderModule = ResourceManager::loadShaderModule(RESOURCE_DIR "/line_shader.wgsl", device);
shaderModule = ResourceManager::loadShaderModule(resolveFile("resources/line_shader.wgsl"), device);
RenderPipelineDescriptor pipelineDesc;

// This is for instanced rendering
Expand Down
7 changes: 2 additions & 5 deletions src/pipelines/PostProcessingPipeline.cpp
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
#include "PostProcessingPipeline.h"
#include "ResourceManager.h"

#ifndef RESOURCE_DIR
#define RESOURCE_DIR "this will be defined by cmake depending on the build type. This define is to disable error squiggles"
#endif
#include "PathFinder.h"

using namespace wgpu;

bool PostProcessingPipeline::init(Device &device, TextureFormat &swapChainFormat, TextureView &textureView)
{
this->device = device;
shaderModule = ResourceManager::loadShaderModule(RESOURCE_DIR "/post_processing.wgsl", device);
shaderModule = ResourceManager::loadShaderModule(resolveFile("resources/post_processing.wgsl"), device);
RenderPipelineDescriptor pipelineDesc;
pipelineDesc.label = "Post process pipeline";

Expand Down