diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 09ab56f31..7a745bb36 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,51 +19,126 @@ jobs: - name: Move binaries shell: cmd run: | - move BUILD\x64\Release\Editor_Windows\Editor_Windows.exe Editor - xcopy WickedEngine\dxcompiler.dll Editor + move BUILD\x64\Release\Editor_Windows\Editor_Windows.exe Editor\ + xcopy WickedEngine\dxcompiler.dll Editor\ - move BUILD\x64\Release\Tests\Tests.exe Tests - xcopy WickedEngine\dxcompiler.dll Tests + move BUILD\x64\Release\Tests\Tests.exe Tests\ + xcopy WickedEngine\dxcompiler.dll Tests\ - name: Package Editor uses: actions/upload-artifact@v2 with: name: Editor (Windows x64) path: | - WickedEngine\shaders\ - WickedEngine\fonts\ - images\ - models\ - scripts\ - Documentation\ - *.txt - *.md - Editor\*.exe - Editor\images\ - Editor\sound\ - Editor\*.ini - Editor\*.ico - Editor\*.lua - Editor\*.dll + WickedEngine/shaders/ + WickedEngine/fonts/ + images/ + models/ + scripts/ + Documentation/ + README.md + LICENSE.md + other_licenses.txt + features.txt + Editor/images/ + Editor/sound/ + Editor/*.ini + Editor/*.ico + Editor/*.lua + Editor/*.exe + Editor/*.dll - name: Package Tests uses: actions/upload-artifact@v2 with: name: Tests (Windows x64) path: | - WickedEngine\shaders\ - WickedEngine\fonts\ - images\ - models\ - scripts\ - Documentation\ - *.txt - *.md - Tests\*.exe - Tests\images\ - Tests\sound\ - Tests\*.ini - Tests\*.ico - Tests\*.lua - Tests\*.dll - Tests\*.ttf + WickedEngine/shaders/ + WickedEngine/fonts/ + images/ + models/ + scripts/ + Documentation/ + README.md + LICENSE.md + other_licenses.txt + features.txt + Tests/images/ + Tests/sound/ + Tests/*.ini + Tests/*.ico + Tests/*.lua + Tests/*.ttf + Tests/*.exe + Tests/*.dll + + + linux: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + + - name: Install dependencies + run: | + wget -qO - https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo apt-key add - + sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-1.2.148-focal.list https://packages.lunarg.com/vulkan/1.2.148/lunarg-vulkan-1.2.148-focal.list + sudo apt update + sudo apt install vulkan-sdk + sudo apt install libsdl2-dev + + - name: Build + run: | + mkdir build + cd build + cmake .. -DCMAKE_BUILD_TYPE=Release + make + + - name: Move binaries + run: | + mv build/Editor/WickedEngineEditor Editor/ + mv build/Tests/Tests Tests/ + + - name: Package Editor + uses: actions/upload-artifact@v2 + with: + name: Editor (Ubuntu 20.04) + path: | + WickedEngine/shaders/ + WickedEngine/fonts/ + images/ + models/ + scripts/ + Documentation/ + README.md + LICENSE.md + other_licenses.txt + features.txt + Editor/images/ + Editor/sound/ + Editor/*.ini + Editor/*.ico + Editor/*.lua + Editor/WickedEngineEditor + + - name: Package Tests + uses: actions/upload-artifact@v2 + with: + name: Tests (Ubuntu 20.04) + path: | + WickedEngine/shaders/ + WickedEngine/fonts/ + images/ + models/ + scripts/ + Documentation/ + README.md + LICENSE.md + other_licenses.txt + features.txt + Tests/images/ + Tests/sound/ + Tests/*.ini + Tests/*.ico + Tests/*.lua + Tests/*.ttf + Tests/Tests diff --git a/.gitignore b/.gitignore index e1e04897c..9fac52d6b 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ [Rr]elease/ x64/ build/ +cmake-build-*/ bld/ [Bb]in/ [Oo]bj/ @@ -192,39 +193,21 @@ ModelManifest.xml /WinRT/WickedEngine /WickedEngine/WinRT /WickedEngine/StoreTest -/WickedEngine/shaders +/WickedEngine/shaders/ /WickedEngine/Release_Store/WickedEngine.tlog /WickedEngine/Release_RT /WickedEngine/Debug_RT /Debug_RT/WickedEngine /WickedEngine/Debug_UWP -/WickedEngine/Scene/Sample/temp -/WickedEngine/nsight_profiler.cfg -/WickedEngine/Scene/Sample/nsight_profiler.cfg -/WickedEngine/Scene/Sponza/temp -/WickedEngine/Scene/Sponza/nsight_profiler.cfg -/WickedEngine/Scene/Sample/asd.wimf -/WickedEngine/models/Sample/temp -/WickedEngine/models/Sample/nsight_profiler.cfg -/WickedEngine/models/Sample/asd.wimf -/WickedEngine/models/Sponza/temp -/WickedEngine/models/Sponza/nsight_profiler.cfg -/WickedEngine/models/Sponza/dirlight.wimf -/WickedEngine/models/Sponza/radiance.wimf -/WickedEngine/models/Sponza/radiance.wiw -/WickedEngine/models/Sponza/reflections.wimf -/WickedEngine/models/Sample/asd.wiw -/WickedEngine/models/Sponza -/WickedEngine/models/Stormtrooper/temp/history0 -/WickedEngine/models/Stormtrooper/temp /.vs/WickedEngine/v15 /.vs/WickedEngine/v15 /.vs/WickedEngine/v15/Browse.VC.db -/WickedEngine/models/Stormtrooper/nsight_profiler.cfg *.cfg -/WickedEngine/models/Sample/nosun.wiw -/WickedEngine/models/Sample/nosun.wimf /.vs -/models/Sample/temp -/models/Emitter/temp *.csv + +# Intellij IDEs +.idea/ + +# Visual studio code project files +.vscode/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..64e58cb1e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.7) + +project(WickedEngine) + +set(CMAKE_CXX_STANDARD 17) + +if (WIN32) + set(PLATFORM "Windows") + add_compile_definitions(WIN32=1) + # add_compile_definitions(_WIN32=1) this is a given from the compiler +elseif(UNIX) + find_package(SDL2 REQUIRED) + set(PLATFORM "SDL2") + add_compile_definitions(SDL2=1) +endif() + +add_subdirectory(WickedEngine) +add_subdirectory(Tests) +add_subdirectory(Editor) diff --git a/Editor/App.cpp b/Editor/App_Windows.cpp similarity index 99% rename from Editor/App.cpp rename to Editor/App_Windows.cpp index 583405f36..d6de4a505 100644 --- a/Editor/App.cpp +++ b/Editor/App_Windows.cpp @@ -1,5 +1,5 @@ #include "stdafx.h" -#include "App.h" +#include "App_Windows.h" #include diff --git a/Editor/App.h b/Editor/App_Windows.h similarity index 100% rename from Editor/App.h rename to Editor/App_Windows.h diff --git a/Editor/CMakeLists.txt b/Editor/CMakeLists.txt new file mode 100644 index 000000000..7c4bc927e --- /dev/null +++ b/Editor/CMakeLists.txt @@ -0,0 +1,38 @@ +find_package(Threads REQUIRED) + +add_executable(WickedEngineEditor + main_${PLATFORM}.cpp + $<$:App_${PLATFORM}.cpp> + AnimationWindow.cpp + CameraWindow.cpp + DecalWindow.cpp + Editor.cpp + EmitterWindow.cpp + EnvProbeWindow.cpp + ForceFieldWindow.cpp + HairParticleWindow.cpp + IKWindow.cpp + LayerWindow.cpp + LightWindow.cpp + MaterialWindow.cpp + MeshWindow.cpp + ModelImporter_GLTF.cpp + ModelImporter_OBJ.cpp + NameWindow.cpp + ObjectWindow.cpp + PaintToolWindow.cpp + PostprocessWindow.cpp + RendererWindow.cpp + SoundWindow.cpp + SpringWindow.cpp + stdafx.cpp + TransformWindow.cpp + Translator.cpp + WeatherWindow.cpp + xatlas.cpp +) + +target_link_libraries(WickedEngineEditor PUBLIC + WickedEngine + Threads::Threads +) diff --git a/Editor/Editor.cpp b/Editor/Editor.cpp index b6070bfcf..301759003 100644 --- a/Editor/Editor.cpp +++ b/Editor/Editor.cpp @@ -6,6 +6,8 @@ #include "Translator.h" #include +#include +#include #ifdef PLATFORM_UWP #include @@ -53,7 +55,7 @@ using namespace wiECS; void Editor::Initialize() { - __super::Initialize(); + MainComponent::Initialize(); infoDisplay.active = true; infoDisplay.watermark = true; @@ -92,7 +94,7 @@ void EditorLoadingScreen::Load() sprite.params.blendFlag = BLENDMODE_ALPHA; AddSprite(&sprite); - __super::Load(); + LoadingScreen::Load(); } void EditorLoadingScreen::Update(float dt) { @@ -100,7 +102,7 @@ void EditorLoadingScreen::Update(float dt) font.params.posY = wiRenderer::GetDevice()->GetScreenHeight()*0.5f; sprite.params.pos = XMFLOAT3(wiRenderer::GetDevice()->GetScreenWidth()*0.5f, wiRenderer::GetDevice()->GetScreenHeight()*0.5f - font.textHeight(), 0); - __super::Update(dt); + LoadingScreen::Update(dt); } @@ -168,10 +170,10 @@ void EditorComponent::ChangeRenderPath(RENDERPATH path) void EditorComponent::ResizeBuffers() { - __super::ResizeBuffers(); + RenderPath2D::ResizeBuffers(); GraphicsDevice* device = wiRenderer::GetDevice(); - HRESULT hr; + bool hr; if(renderPath != nullptr && renderPath->GetDepthStencil() != nullptr) { @@ -185,13 +187,13 @@ void EditorComponent::ResizeBuffers() { desc.SampleCount = renderPath->getMSAASampleCount(); hr = device->CreateTexture(&desc, nullptr, &rt_selectionOutline_MSAA); - assert(SUCCEEDED(hr)); + assert(hr); desc.SampleCount = 1; } hr = device->CreateTexture(&desc, nullptr, &rt_selectionOutline[0]); - assert(SUCCEEDED(hr)); + assert(hr); hr = device->CreateTexture(&desc, nullptr, &rt_selectionOutline[1]); - assert(SUCCEEDED(hr)); + assert(hr); { RenderPassDesc desc; @@ -212,7 +214,7 @@ void EditorComponent::ResizeBuffers() ) ); hr = device->CreateRenderPass(&desc, &renderpass_selectionOutline[0]); - assert(SUCCEEDED(hr)); + assert(hr); if (renderPath->getMSAASampleCount() == 1) { @@ -223,14 +225,14 @@ void EditorComponent::ResizeBuffers() desc.attachments[1].texture = &rt_selectionOutline[1]; // resolve } hr = device->CreateRenderPass(&desc, &renderpass_selectionOutline[1]); - assert(SUCCEEDED(hr)); + assert(hr); } } } void EditorComponent::ResizeLayout() { - __super::ResizeLayout(); + RenderPath2D::ResizeLayout(); // GUI elements scaling: @@ -970,15 +972,15 @@ void EditorComponent::Load() wiJobSystem::Wait(ctx); - __super::Load(); + RenderPath2D::Load(); } void EditorComponent::Start() { - __super::Start(); + RenderPath2D::Start(); } void EditorComponent::FixedUpdate() { - __super::FixedUpdate(); + RenderPath2D::FixedUpdate(); renderPath->FixedUpdate(); } @@ -1243,7 +1245,7 @@ void EditorComponent::Update(float dt) if (!wiBackLog::isActive() && !GetGUI().HasFocus()) { // Begin picking: - UINT pickMask = rendererWnd->GetPickType(); + unsigned int pickMask = rendererWnd->GetPickType(); RAY pickRay = wiRenderer::GetPickRay((long)currentMouse.x, (long)currentMouse.y); { hovered = wiScene::PickResult(); @@ -1763,7 +1765,7 @@ void EditorComponent::Update(float dt) wiProfiler::EndRange(profrange); - __super::Update(dt); + RenderPath2D::Update(dt); renderPath->Update(dt); } @@ -1942,7 +1944,7 @@ void EditorComponent::Render() const device->EventEnd(cmd); } - __super::Render(); + RenderPath2D::Render(); } void EditorComponent::Compose(CommandList cmd) const @@ -1955,7 +1957,7 @@ void EditorComponent::Compose(CommandList cmd) const } // Draw selection outline to the screen: - const float selectionColorIntensity = std::sinf(selectionOutlineTimer * XM_2PI * 0.8f) * 0.5f + 0.5f; + const float selectionColorIntensity = std::sin(selectionOutlineTimer * XM_2PI * 0.8f) * 0.5f + 0.5f; if (renderPath->GetDepthStencil() != nullptr && !translator.selected.empty()) { GraphicsDevice* device = wiRenderer::GetDevice(); @@ -2275,7 +2277,7 @@ void EditorComponent::Compose(CommandList cmd) const translator.Draw(camera, cmd); } - __super::Compose(cmd); + RenderPath2D::Compose(cmd); } void EditorComponent::ClearSelected() diff --git a/Editor/Editor_UWP.vcxproj b/Editor/Editor_UWP.vcxproj index 050293b18..e7a870d84 100644 --- a/Editor/Editor_UWP.vcxproj +++ b/Editor/Editor_UWP.vcxproj @@ -431,10 +431,10 @@ - + - + diff --git a/Editor/Editor_UWP.vcxproj.filters b/Editor/Editor_UWP.vcxproj.filters index 7a785922a..da305ced7 100644 --- a/Editor/Editor_UWP.vcxproj.filters +++ b/Editor/Editor_UWP.vcxproj.filters @@ -25,10 +25,10 @@ - + - + diff --git a/Editor/Editor_Windows.vcxproj b/Editor/Editor_Windows.vcxproj index 81c9d436c..b18e2d7bf 100644 --- a/Editor/Editor_Windows.vcxproj +++ b/Editor/Editor_Windows.vcxproj @@ -208,13 +208,10 @@ - + - - - @@ -233,6 +230,9 @@ {06163dcb-b183-4ed9-9c62-13ef1658e049} + + + diff --git a/Editor/PaintToolWindow.cpp b/Editor/PaintToolWindow.cpp index 992950ef9..61d63c261 100644 --- a/Editor/PaintToolWindow.cpp +++ b/Editor/PaintToolWindow.cpp @@ -4,6 +4,7 @@ #include "ShaderInterop_Paint.h" #include +#include using namespace wiECS; using namespace wiScene; @@ -289,6 +290,8 @@ void PaintToolWindow::Update(float dt) int uvset = 0; auto resource = GetEditTextureSlot(*material, &uvset); + if (resource == nullptr) + break; const TextureDesc& desc = resource->texture->GetDesc(); auto& vertex_uvset = uvset == 0 ? mesh->vertex_uvset_0 : mesh->vertex_uvset_1; @@ -360,6 +363,8 @@ void PaintToolWindow::Update(float dt) paintrad.radius = radius; paintrad.center = center; paintrad.uvset = uvset; + paintrad.dimensions.x = desc.Width; + paintrad.dimensions.y = desc.Height; wiRenderer::DrawPaintRadius(paintrad); } break; @@ -472,7 +477,7 @@ void PaintToolWindow::Update(float dt) { RecordHistory(true); rebuild = true; - const float affection = amount * std::powf(1 - (dist / radius), falloff); + const float affection = amount * std::pow(1.0f - (dist / radius), falloff); switch (mode) { @@ -599,7 +604,7 @@ void PaintToolWindow::Update(float dt) if (z >= 0 && z <= 1 && dist <= radius) { averageNormal += N; - const float affection = amount * std::powf(1 - (dist / radius), falloff); + const float affection = amount * std::pow(1.0f - (dist / radius), falloff); paintindices.push_back({ j, affection }); } } @@ -807,7 +812,7 @@ void PaintToolWindow::Update(float dt) case MODE_HAIRPARTICLE_LENGTH: if (hair->vertex_lengths[j] > 0) // don't change distribution { - const float affection = amount * std::powf(1 - (dist / radius), falloff); + const float affection = amount * std::pow(1.0f - (dist / radius), falloff); hair->vertex_lengths[j] = wiMath::Lerp(hair->vertex_lengths[j], color_float.w, affection); // don't let it "remove" the vertex by keeping its length above zero: // (because if removed, distribution also changes which might be distracting) diff --git a/Editor/RendererWindow.cpp b/Editor/RendererWindow.cpp index 7aefcc4a4..2e561589b 100644 --- a/Editor/RendererWindow.cpp +++ b/Editor/RendererWindow.cpp @@ -645,9 +645,9 @@ RendererWindow::~RendererWindow() delete rendererWindow; } -UINT RendererWindow::GetPickType() +uint32_t RendererWindow::GetPickType() { - UINT pickType = PICK_VOID; + uint32_t pickType = PICK_VOID; if (pickTypeObjectCheckBox->GetCheck()) { pickType |= PICK_OBJECT; diff --git a/Editor/RendererWindow.h b/Editor/RendererWindow.h index a3a460c41..b445ef9da 100644 --- a/Editor/RendererWindow.h +++ b/Editor/RendererWindow.h @@ -84,6 +84,6 @@ public: wiCheckBox* freezeCullingCameraCheckBox; - UINT GetPickType(); + uint32_t GetPickType(); }; diff --git a/Editor/Translator.cpp b/Editor/Translator.cpp index 6dd3a90df..62fa45654 100644 --- a/Editor/Translator.cpp +++ b/Editor/Translator.cpp @@ -15,9 +15,9 @@ PipelineState pso_wirepart; GPUBuffer vertexBuffer_Axis; GPUBuffer vertexBuffer_Plane; GPUBuffer vertexBuffer_Origin; -UINT vertexCount_Axis = 0; -UINT vertexCount_Plane = 0; -UINT vertexCount_Origin = 0; +uint32_t vertexCount_Axis = 0; +uint32_t vertexCount_Plane = 0; +uint32_t vertexCount_Origin = 0; float origin_size = 0.2f; namespace Translator_Internal @@ -458,7 +458,7 @@ void Translator::Draw(const CameraComponent& camera, CommandList cmd) const const GPUBuffer* vbs[] = { &vertexBuffer_Plane, }; - const UINT strides[] = { + const uint32_t strides[] = { sizeof(XMFLOAT4) + sizeof(XMFLOAT4), }; device->BindVertexBuffers(vbs, 0, arraysize(vbs), strides, nullptr, cmd); @@ -494,7 +494,7 @@ void Translator::Draw(const CameraComponent& camera, CommandList cmd) const const GPUBuffer* vbs[] = { &vertexBuffer_Axis, }; - const UINT strides[] = { + const uint32_t strides[] = { sizeof(XMFLOAT4) + sizeof(XMFLOAT4), }; device->BindVertexBuffers(vbs, 0, arraysize(vbs), strides, nullptr, cmd); @@ -530,7 +530,7 @@ void Translator::Draw(const CameraComponent& camera, CommandList cmd) const const GPUBuffer* vbs[] = { &vertexBuffer_Origin, }; - const UINT strides[] = { + const uint32_t strides[] = { sizeof(XMFLOAT4) + sizeof(XMFLOAT4), }; device->BindVertexBuffers(vbs, 0, arraysize(vbs), strides, nullptr, cmd); diff --git a/Editor/main_SDL2.cpp b/Editor/main_SDL2.cpp new file mode 100644 index 000000000..fbe5a60b5 --- /dev/null +++ b/Editor/main_SDL2.cpp @@ -0,0 +1,73 @@ +#include "stdafx.h" +#include "Editor.h" + +#include + +#include "stdafx.h" +#include +#include "sdl2.h" + +int sdl_loop(Editor &editor) +{ + SDL_Event event; + + bool quit = false; + while (!quit) + { + SDL_PumpEvents(); + editor.Run(); + + int ret = SDL_PollEvent(&event); + + if (ret < 0) { + std::cerr << "Error Peeping event: " << SDL_GetError() << std::endl; + std::cerr << "Exiting now" << std::endl; + return -1; + } + + if (ret > 0) { + if (event.type == SDL_WINDOWEVENT) { + switch (event.window.event) { + case SDL_WINDOWEVENT_CLOSE: // exit game + //editor.Quit(); + quit = true; + + default: + break; + } + } + } + + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + Editor editor; + + wiStartupArguments::Parse(argc, argv); + + sdl2::sdlsystem_ptr_t system = sdl2::make_sdlsystem(SDL_INIT_EVERYTHING | SDL_INIT_EVENTS); + if (!system) { + throw sdl2::SDLError("Error creating SDL2 system"); + } + + //TODO read config.ini + sdl2::window_ptr_t window = sdl2::make_window( + "Wicked Engine Editor", + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + 1920, 1080, + SDL_WINDOW_SHOWN | SDL_WINDOW_VULKAN); + if (!window) { + throw sdl2::SDLError("Error creating window"); + } + + editor.SetWindow(window.get()); + + int ret = sdl_loop(editor); + + SDL_Quit(); + return ret; +} diff --git a/Editor/main.cpp b/Editor/main_Windows.cpp similarity index 99% rename from Editor/main.cpp rename to Editor/main_Windows.cpp index 2a8379f14..4d8e2333d 100644 --- a/Editor/main.cpp +++ b/Editor/main_Windows.cpp @@ -1,5 +1,5 @@ #include "stdafx.h" -#include "main.h" +#include "main_Windows.h" #include "Editor.h" #include diff --git a/Editor/main.h b/Editor/main_Windows.h similarity index 100% rename from Editor/main.h rename to Editor/main_Windows.h diff --git a/README.md b/README.md index 66fdbdaa6..e9a32bb8c 100644 --- a/README.md +++ b/README.md @@ -29,32 +29,47 @@ You can download the engine by using Git and cloning the repository, or download ### Platforms: - Windows 10 Desktop (x86, x64) [Visual Studio 2019] - UWP (x86, x64, ARM, Phone, XBOX One) [Visual Studio 2019] -- Linux (experimental branch) [CMake] +- Linux [CMake 3.7] ### How to build: #### Windows -To build Wicked Engine for Windows 10, use Visual Studio and the provided solution file. Make sure that you have the latest Windows SDK and updated operating system. There are a couple of projects that you can run up front: Editor, Tests and Template. You just have to set either as startup project and press F5 in Visual Studio to build and run. - -If you wish to integrate Wicked Engine into your own project, you can use it as a static library and link it to your application. For this, you must first compile the engine library project for the desired platform. For Windows Desktop, this is the WickedEngine_Windows project. After that, set the following dependencies to this library in Visual Studio this way in the implementing project (paths are as if your project is inside the engine root folder): +To build Wicked Engine for Windows 10, use Visual Studio and the provided solution file. Make sure that you have the latest Windows SDK and updated operating system. There are a couple of projects that you can run up front: Editor, Tests and Template. You just have to set either as startup project and press F5 in Visual Studio to build and run. For optimal performance, choose `Release` mode, for the best debugging experience, choose `Debug` mode. -1. Open Project Properties -> Configuration Properties -2. C/C++ -> General -> Additional Include Directories: - - ./WickedEngine -3. Linker -> General -> Additional Library Directories: - - Directory of your built .lib file (For example ./x64/Release) -4. Compile with a non-DLL runtime library for Release builds: - - Project settings -> C/C++ -> Code Generation -> Runtime Library -> Multi threaded -5. If you want to create a UWP application, link against the WickedEngine_UWP library. - -When your project settings are set up, put `#include "WickedEngine.h"` in your source. This will enable the use of all the engine features and link the necessary binaries. After this, you should already be able to build your project. +If you want to develop an application that uses Wicked Engine, you will have to link to WickedEngine_Windows.lib or WickedEngine_UWP.lib.lib and `#include "WickedEngine.h"` into the source code. For examples, look at the Template, Editor and Tests projects in Visual Studio that do this. + +You can also dowload prebuilt and packaged versions of the Editor and Tests here: [![Github Build Status](https://github.com/turanszkij/WickedEngine/workflows/Build/badge.svg)](https://github.com/turanszkij/WickedEngine/actions) + +If you have questions or stuck, please use the `windows` communication channel on Discord: [![Discord chat](https://img.shields.io/discord/602811659224088577?logo=discord)](https://discord.gg/CFjRYmE) -If you have trouble, you can look at or copy the project settings for Editor, Tests and Template application projects to get an idea how to link with Wicked Engine. #### Linux -The Linux branch is experimental. The steps to build can be found here. If you have questions or stuck, please use the `linux` communication channel on Discord: [![Discord chat](https://img.shields.io/discord/602811659224088577?logo=discord)](https://discord.gg/CFjRYmE) +The Linux support is experimental. You can find a sample build script for Ubuntu 20.04 [here](.github/workflows/build.yml) (in the linux section). You might need to install some dependencies, such as Vulkan SDK 1.2 or greater, SDL2, cmake 3.7 and g++ compiler (C++ 17 compliant version). For Ubuntu 20.04, you can use the following commands to install dependencies: +```bash +wget -qO - https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo apt-key add - +sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-1.2.148-focal.list https://packages.lunarg.com/vulkan/1.2.148/lunarg-vulkan-1.2.148-focal.list +sudo apt update +sudo apt install vulkan-sdk +sudo apt install libsdl2-dev +sudo apt install build-essential +``` + - Note: The Vulkan SDK for Ubuntu contains DXC (DirectXShaderCompiler) which is required to build the shaders. If you are using an other Linux distribution, make sure that you have DirectXShaderCompiler. +To build the engine, editor and tests, use Cmake and make: +```bash +mkdir build +cd build +cmake .. -DCMAKE_BUILD_TYPE=Release +make +``` + +If you want to develop an application that uses Wicked Engine, you will have to link to libWickedEngine.a and `#include "WickedEngine.h"` into the source code. For examples, look at the Cmake files. + +You can also dowload prebuilt and packaged versions of the Editor and Tests here: [![Github Build Status](https://github.com/turanszkij/WickedEngine/workflows/Build/badge.svg)](https://github.com/turanszkij/WickedEngine/actions) + +If you have questions or stuck, please use the `linux` communication channel on Discord: [![Discord chat](https://img.shields.io/discord/602811659224088577?logo=discord)](https://discord.gg/CFjRYmE) + ### Examples: @@ -182,7 +197,7 @@ The preferred workflow is to import models into the Editor, and save them as ### Graphics API: -The default renderer is DirectX 11. There is also a DirectX12 renderer and Vulkan renderer. +The default renderer is DirectX 11 on Windows and Vulkan on Linux. There is also an optional DirectX12 renderer for Windows. You can specify command line arguments (without any prefix) to switch between render devices or other settings. Currently the list of options: @@ -211,6 +226,7 @@ HLSL6 shaders can be compiled by Rebuilding the Shaders_HLSL6 project from withi * **Vulkan support will be built into the application if the Vulkan SDK is installed on the build machine. Vulkan will try to load shaders from WickedEngine/shaders/spirv directory. SPIRV shaders can be compiled by Rebuilding the Shaders_SPIRV project from within Visual Studio (Python 3 required for building). +For Linux builds, spirv shaders will be built by using Cmake system.
diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt new file mode 100644 index 000000000..a99e4c213 --- /dev/null +++ b/Tests/CMakeLists.txt @@ -0,0 +1,12 @@ +find_package(Threads REQUIRED) + +add_executable(Tests + main_SDL2.cpp + stdafx.cpp + Tests.cpp +) + +target_link_libraries(Tests PUBLIC + WickedEngine + Threads::Threads +) diff --git a/Tests/Tests.cpp b/Tests/Tests.cpp index 83487a840..183da4238 100644 --- a/Tests/Tests.cpp +++ b/Tests/Tests.cpp @@ -4,13 +4,14 @@ #include #include #include +#include using namespace wiECS; using namespace wiScene; void Tests::Initialize() { - __super::Initialize(); + MainComponent::Initialize(); infoDisplay.active = true; infoDisplay.watermark = true; @@ -25,7 +26,7 @@ void Tests::Initialize() void TestsRenderer::ResizeLayout() { - __super::ResizeLayout(); + RenderPath3D_TiledForward::ResizeLayout(); float screenW = wiRenderer::GetDevice()->GetScreenWidth(); float screenH = wiRenderer::GetDevice()->GetScreenHeight(); @@ -127,7 +128,9 @@ void TestsRenderer::Load() wiScene::GetScene().weather = WeatherComponent(); this->ClearSprites(); this->ClearFonts(); - wiLua::KillProcesses(); + if (wiLua::GetLuaState() != nullptr) { + wiLua::KillProcesses(); + } // Reset camera position: TransformComponent transform; @@ -294,12 +297,37 @@ void TestsRenderer::Load() testSelector->SetSelected(0); GetGUI().AddWidget(testSelector); - __super::Load(); + RenderPath3D_TiledForward::Load(); } void TestsRenderer::Update(float dt) { switch (testSelector->GetSelected()) { + case 1: + { + Scene& scene = wiScene::GetScene(); + // teapot_material Base Base_mesh Top Top_mesh editorLight + wiECS::Entity e_teapot_base = scene.Entity_FindByName("Base"); + wiECS::Entity e_teapot_top = scene.Entity_FindByName("Top"); + assert(e_teapot_base != wiECS::INVALID_ENTITY); + assert(e_teapot_top != wiECS::INVALID_ENTITY); + TransformComponent* transform_base = scene.transforms.GetComponent(e_teapot_base); + TransformComponent* transform_top = scene.transforms.GetComponent(e_teapot_top); + assert(transform_base != nullptr); + assert(transform_top != nullptr); + float rotation = dt; + if (wiInput::Down(wiInput::KEYBOARD_BUTTON_LEFT)) + { + transform_base->Rotate(XMVectorSet(0,rotation,0,1)); + transform_top->Rotate(XMVectorSet(0,rotation,0,1)); + } + else if (wiInput::Down(wiInput::KEYBOARD_BUTTON_RIGHT)) + { + transform_base->Rotate(XMVectorSet(0,-rotation,0,1)); + transform_top->Rotate(XMVectorSet(0,-rotation,0,1)); + } + } + break; case 17: { if (ik_entity != INVALID_ENTITY) @@ -335,7 +363,7 @@ void TestsRenderer::Update(float dt) break; } - __super::Update(dt); + RenderPath3D_TiledForward::Update(dt); } void TestsRenderer::RunJobSystemTest() diff --git a/Tests/Tests.vcxproj b/Tests/Tests.vcxproj index a58f18e86..a84f255b1 100644 --- a/Tests/Tests.vcxproj +++ b/Tests/Tests.vcxproj @@ -172,14 +172,14 @@ - + - + Create Create diff --git a/Tests/Tests.vcxproj.filters b/Tests/Tests.vcxproj.filters index 679a04101..8c4e1a2fc 100644 --- a/Tests/Tests.vcxproj.filters +++ b/Tests/Tests.vcxproj.filters @@ -28,7 +28,7 @@ Code - + Code @@ -39,7 +39,7 @@ Code - + Code diff --git a/Tests/main_SDL2.cpp b/Tests/main_SDL2.cpp new file mode 100644 index 000000000..b2c1b399d --- /dev/null +++ b/Tests/main_SDL2.cpp @@ -0,0 +1,78 @@ +// WickedEngineTests.cpp : Defines the entry point for the application. +// + +#include "stdafx.h" +#include +#include "sdl2.h" + +int sdl_loop(Tests &tests) +{ + SDL_Event event; + + bool quit = false; + while (!quit) + { + SDL_PumpEvents(); + tests.Run(); + +// int ret = SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT); + int ret = SDL_PollEvent(&event); + + if (ret < 0) { + std::cerr << "Error Peeping event: " << SDL_GetError() << std::endl; + std::cerr << "Exiting now" << std::endl; + return -1; + } + + if (ret > 0) { + if (event.type == SDL_WINDOWEVENT) { + switch (event.window.event) { + case SDL_WINDOWEVENT_CLOSE: // exit game + //tests.Quit(); + quit = true; + + default: + break; + } + } +// else if (event.type == SDL_KEYDOWN) { +// switch (event.key.keysym.scancode) { +// case SDL_SCANCODE_SPACE: +// tests.SetSelected(tests.GetSelected()+1); +// } +// } + } + + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + Tests tests; + // TODO: Place code here. + + wiStartupArguments::Parse(argc, argv); + + sdl2::sdlsystem_ptr_t system = sdl2::make_sdlsystem(SDL_INIT_EVERYTHING | SDL_INIT_EVENTS); + if (!system) { + throw sdl2::SDLError("Error creating SDL2 system"); + } + + sdl2::window_ptr_t window = sdl2::make_window( + "Wicked Engine Tests", + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + 1280, 800, + SDL_WINDOW_SHOWN | SDL_WINDOW_VULKAN); + if (!window) { + throw sdl2::SDLError("Error creating window"); + } + + tests.SetWindow(window.get()); + + int ret = sdl_loop(tests); + + SDL_Quit(); + return ret; +} diff --git a/Tests/main.cpp b/Tests/main_Windows.cpp similarity index 99% rename from Tests/main.cpp rename to Tests/main_Windows.cpp index 1cb2c556c..fcb4c6dc2 100644 --- a/Tests/main.cpp +++ b/Tests/main_Windows.cpp @@ -2,7 +2,7 @@ // #include "stdafx.h" -#include "main.h" +#include "main_Windows.h" #define MAX_LOADSTRING 100 diff --git a/Tests/main.h b/Tests/main_Windows.h similarity index 100% rename from Tests/main.h rename to Tests/main_Windows.h diff --git a/WickedEngine/BULLET/CMakeLists.txt b/WickedEngine/BULLET/CMakeLists.txt new file mode 100644 index 000000000..55a6e0ea7 --- /dev/null +++ b/WickedEngine/BULLET/CMakeLists.txt @@ -0,0 +1,146 @@ +project(BULLET) + +add_library(Bullet STATIC + BulletCollision/BroadphaseCollision/btAxisSweep3.cpp + BulletCollision/BroadphaseCollision/btBroadphaseProxy.cpp + BulletCollision/BroadphaseCollision/btCollisionAlgorithm.cpp + BulletCollision/BroadphaseCollision/btDbvt.cpp + BulletCollision/BroadphaseCollision/btDbvtBroadphase.cpp + BulletCollision/BroadphaseCollision/btDispatcher.cpp + BulletCollision/BroadphaseCollision/btMultiSapBroadphase.cpp + BulletCollision/BroadphaseCollision/btOverlappingPairCache.cpp + BulletCollision/BroadphaseCollision/btQuantizedBvh.cpp + BulletCollision/BroadphaseCollision/btSimpleBroadphase.cpp + BulletCollision/CollisionDispatch/SphereTriangleDetector.cpp + BulletCollision/CollisionDispatch/btActivatingCollisionAlgorithm.cpp + BulletCollision/CollisionDispatch/btBox2dBox2dCollisionAlgorithm.cpp + BulletCollision/CollisionDispatch/btBoxBoxCollisionAlgorithm.cpp + BulletCollision/CollisionDispatch/btBoxBoxDetector.cpp + BulletCollision/CollisionDispatch/btCollisionDispatcher.cpp + BulletCollision/CollisionDispatch/btCollisionObject.cpp + BulletCollision/CollisionDispatch/btCollisionWorld.cpp + BulletCollision/CollisionDispatch/btCompoundCollisionAlgorithm.cpp + BulletCollision/CollisionDispatch/btCompoundCompoundCollisionAlgorithm.cpp + BulletCollision/CollisionDispatch/btConvex2dConvex2dAlgorithm.cpp + BulletCollision/CollisionDispatch/btConvexConcaveCollisionAlgorithm.cpp + BulletCollision/CollisionDispatch/btConvexConvexAlgorithm.cpp + BulletCollision/CollisionDispatch/btConvexPlaneCollisionAlgorithm.cpp + BulletCollision/CollisionDispatch/btDefaultCollisionConfiguration.cpp + BulletCollision/CollisionDispatch/btEmptyCollisionAlgorithm.cpp + BulletCollision/CollisionDispatch/btGhostObject.cpp + BulletCollision/CollisionDispatch/btHashedSimplePairCache.cpp + BulletCollision/CollisionDispatch/btInternalEdgeUtility.cpp + BulletCollision/CollisionDispatch/btManifoldResult.cpp + BulletCollision/CollisionDispatch/btSimulationIslandManager.cpp + BulletCollision/CollisionDispatch/btSphereBoxCollisionAlgorithm.cpp + BulletCollision/CollisionDispatch/btSphereSphereCollisionAlgorithm.cpp + BulletCollision/CollisionDispatch/btSphereTriangleCollisionAlgorithm.cpp + BulletCollision/CollisionDispatch/btUnionFind.cpp + BulletCollision/CollisionShapes/btBox2dShape.cpp + BulletCollision/CollisionShapes/btBoxShape.cpp + BulletCollision/CollisionShapes/btBvhTriangleMeshShape.cpp + BulletCollision/CollisionShapes/btCapsuleShape.cpp + BulletCollision/CollisionShapes/btCollisionShape.cpp + BulletCollision/CollisionShapes/btCompoundShape.cpp + BulletCollision/CollisionShapes/btConcaveShape.cpp + BulletCollision/CollisionShapes/btConeShape.cpp + BulletCollision/CollisionShapes/btConvex2dShape.cpp + BulletCollision/CollisionShapes/btConvexHullShape.cpp + BulletCollision/CollisionShapes/btConvexInternalShape.cpp + BulletCollision/CollisionShapes/btConvexPointCloudShape.cpp + BulletCollision/CollisionShapes/btConvexPolyhedron.cpp + BulletCollision/CollisionShapes/btConvexShape.cpp + BulletCollision/CollisionShapes/btConvexTriangleMeshShape.cpp + BulletCollision/CollisionShapes/btCylinderShape.cpp + BulletCollision/CollisionShapes/btEmptyShape.cpp + BulletCollision/CollisionShapes/btHeightfieldTerrainShape.cpp + BulletCollision/CollisionShapes/btMinkowskiSumShape.cpp + BulletCollision/CollisionShapes/btMultiSphereShape.cpp + BulletCollision/CollisionShapes/btMultimaterialTriangleMeshShape.cpp + BulletCollision/CollisionShapes/btOptimizedBvh.cpp + BulletCollision/CollisionShapes/btPolyhedralConvexShape.cpp + BulletCollision/CollisionShapes/btScaledBvhTriangleMeshShape.cpp + BulletCollision/CollisionShapes/btShapeHull.cpp + BulletCollision/CollisionShapes/btSphereShape.cpp + BulletCollision/CollisionShapes/btStaticPlaneShape.cpp + BulletCollision/CollisionShapes/btStridingMeshInterface.cpp + BulletCollision/CollisionShapes/btTetrahedronShape.cpp + BulletCollision/CollisionShapes/btTriangleBuffer.cpp + BulletCollision/CollisionShapes/btTriangleCallback.cpp + BulletCollision/CollisionShapes/btTriangleIndexVertexArray.cpp + BulletCollision/CollisionShapes/btTriangleIndexVertexMaterialArray.cpp + BulletCollision/CollisionShapes/btTriangleMesh.cpp + BulletCollision/CollisionShapes/btTriangleMeshShape.cpp + BulletCollision/CollisionShapes/btUniformScalingShape.cpp + BulletCollision/Gimpact/btContactProcessing.cpp + BulletCollision/Gimpact/btGImpactBvh.cpp + BulletCollision/Gimpact/btGImpactCollisionAlgorithm.cpp + BulletCollision/Gimpact/btGImpactQuantizedBvh.cpp + BulletCollision/Gimpact/btGImpactShape.cpp + BulletCollision/Gimpact/btGenericPoolAllocator.cpp + BulletCollision/Gimpact/btTriangleShapeEx.cpp + BulletCollision/Gimpact/gim_box_set.cpp + BulletCollision/Gimpact/gim_contact.cpp + BulletCollision/Gimpact/gim_memory.cpp + BulletCollision/Gimpact/gim_tri_collision.cpp + BulletCollision/NarrowPhaseCollision/btContinuousConvexCollision.cpp + BulletCollision/NarrowPhaseCollision/btConvexCast.cpp + BulletCollision/NarrowPhaseCollision/btGjkConvexCast.cpp + BulletCollision/NarrowPhaseCollision/btGjkEpa2.cpp + BulletCollision/NarrowPhaseCollision/btGjkEpaPenetrationDepthSolver.cpp + BulletCollision/NarrowPhaseCollision/btGjkPairDetector.cpp + BulletCollision/NarrowPhaseCollision/btMinkowskiPenetrationDepthSolver.cpp + BulletCollision/NarrowPhaseCollision/btPersistentManifold.cpp + BulletCollision/NarrowPhaseCollision/btPolyhedralContactClipping.cpp + BulletCollision/NarrowPhaseCollision/btRaycastCallback.cpp + BulletCollision/NarrowPhaseCollision/btSubSimplexConvexCast.cpp + BulletCollision/NarrowPhaseCollision/btVoronoiSimplexSolver.cpp + BulletDynamics/Character/btKinematicCharacterController.cpp + BulletDynamics/ConstraintSolver/btConeTwistConstraint.cpp + BulletDynamics/ConstraintSolver/btContactConstraint.cpp + BulletDynamics/ConstraintSolver/btFixedConstraint.cpp + BulletDynamics/ConstraintSolver/btGearConstraint.cpp + BulletDynamics/ConstraintSolver/btGeneric6DofConstraint.cpp + BulletDynamics/ConstraintSolver/btGeneric6DofSpringConstraint.cpp + BulletDynamics/ConstraintSolver/btHinge2Constraint.cpp + BulletDynamics/ConstraintSolver/btHingeConstraint.cpp + BulletDynamics/ConstraintSolver/btPoint2PointConstraint.cpp + BulletDynamics/ConstraintSolver/btSequentialImpulseConstraintSolver.cpp + BulletDynamics/ConstraintSolver/btSliderConstraint.cpp + BulletDynamics/ConstraintSolver/btSolve2LinearConstraint.cpp + BulletDynamics/ConstraintSolver/btTypedConstraint.cpp + BulletDynamics/ConstraintSolver/btUniversalConstraint.cpp + BulletDynamics/Dynamics/Bullet-C-API.cpp + BulletDynamics/Dynamics/btDiscreteDynamicsWorld.cpp + BulletDynamics/Dynamics/btRigidBody.cpp + BulletDynamics/Dynamics/btSimpleDynamicsWorld.cpp + BulletDynamics/Featherstone/btMultiBody.cpp + BulletDynamics/Featherstone/btMultiBodyConstraint.cpp + BulletDynamics/Featherstone/btMultiBodyConstraintSolver.cpp + BulletDynamics/Featherstone/btMultiBodyDynamicsWorld.cpp + BulletDynamics/Featherstone/btMultiBodyJointLimitConstraint.cpp + BulletDynamics/Featherstone/btMultiBodyJointMotor.cpp + BulletDynamics/Featherstone/btMultiBodyPoint2Point.cpp + BulletDynamics/MLCPSolvers/btDantzigLCP.cpp + BulletDynamics/MLCPSolvers/btMLCPSolver.cpp + BulletDynamics/Vehicle/btRaycastVehicle.cpp + BulletDynamics/Vehicle/btWheelInfo.cpp + BulletSoftBody/btDefaultSoftBodySolver.cpp + BulletSoftBody/btSoftBody.cpp + BulletSoftBody/btSoftBodyConcaveCollisionAlgorithm.cpp + BulletSoftBody/btSoftBodyHelpers.cpp + BulletSoftBody/btSoftBodyRigidBodyCollisionConfiguration.cpp + BulletSoftBody/btSoftRigidCollisionAlgorithm.cpp + BulletSoftBody/btSoftRigidDynamicsWorld.cpp + BulletSoftBody/btSoftSoftCollisionAlgorithm.cpp + LinearMath/btAlignedAllocator.cpp + LinearMath/btConvexHull.cpp + LinearMath/btConvexHullComputer.cpp + LinearMath/btGeometryUtil.cpp + LinearMath/btPolarDecomposition.cpp + LinearMath/btQuickprof.cpp + LinearMath/btSerializer.cpp + LinearMath/btVector3.cpp +) + +target_include_directories(Bullet PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) \ No newline at end of file diff --git a/WickedEngine/CMakeLists.txt b/WickedEngine/CMakeLists.txt new file mode 100644 index 000000000..f4a25edd1 --- /dev/null +++ b/WickedEngine/CMakeLists.txt @@ -0,0 +1,108 @@ +find_package(Vulkan REQUIRED) +find_package(SDL2 REQUIRED) + +add_subdirectory(BULLET) +add_subdirectory(LUA) +add_subdirectory(Utility) +add_subdirectory(shaders) + +add_library(WickedEngine STATIC + LoadingScreen.cpp + LoadingScreen_BindLua.cpp + MainComponent.cpp + MainComponent_BindLua.cpp + Matrix_BindLua.cpp + RenderPath_BindLua.cpp + RenderPath2D.cpp + RenderPath2D_BindLua.cpp + RenderPath3D.cpp + RenderPath3D_BindLua.cpp + RenderPath3D_Deferred.cpp + RenderPath3D_Deferred_BindLua.cpp + RenderPath3D_Forward.cpp + RenderPath3D_Forward_BindLua.cpp + RenderPath3D_PathTracing.cpp + RenderPath3D_TiledDeferred.cpp + RenderPath3D_TiledDeferred_BindLua.cpp + RenderPath3D_TiledForward.cpp + RenderPath3D_TiledForward_BindLua.cpp + SpriteAnim_BindLua.cpp + Texture_BindLua.cpp + Vector_BindLua.cpp + wiArchive.cpp + wiAudio.cpp + wiAudio_BindLua.cpp + wiBackLog.cpp + wiBackLog_BindLua.cpp + wiEmittedParticle.cpp + wiEvent.cpp + wiFadeManager.cpp + wiFFTGenerator.cpp + wiFont.cpp + wiGPUBVH.cpp + wiGPUSortLib.cpp + wiGraphicsDevice.cpp + # wiGraphicsDevice_DX11.cpp + # wiGraphicsDevice_DX12.cpp + wiGraphicsDevice_Vulkan.cpp + wiGUI.cpp + wiHairParticle.cpp + wiHelper.cpp + wiImage.cpp + wiImageParams_BindLua.cpp + wiInitializer.cpp + wiInput.cpp + wiInput_BindLua.cpp + wiIntersect.cpp + wiIntersect_BindLua.cpp + wiJobSystem.cpp + wiLua.cpp + wiMath.cpp + wiNetwork_BindLua.cpp + wiNetwork_Linux.cpp + # wiNetwork_UWP.cpp + # wiNetwork_Windows.cpp + wiOcean.cpp + wiPhysicsEngine_Bullet.cpp + wiProfiler.cpp + wiRandom.cpp + wiRawInput.cpp + wiRectPacker.cpp + wiRenderer.cpp + wiRenderer_BindLua.cpp + wiResourceManager.cpp + wiScene.cpp + wiScene_BindLua.cpp + wiScene_Serializers.cpp + wiSDLInput.cpp + wiSprite.cpp + wiSprite_BindLua.cpp + wiSpriteFont.cpp + wiSpriteFont_BindLua.cpp + wiStartupArguments.cpp + wiTextureHelper.cpp + wiTimer.cpp + wiVersion.cpp + wiWidget.cpp + wiXInput.cpp +) + +target_include_directories(WickedEngine PUBLIC + ${SDL2_INCLUDE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_link_libraries(WickedEngine PUBLIC + Vulkan::Vulkan + ${SDL2_LIBRARIES} + Bullet + LUA + Utility +) + +add_dependencies(WickedEngine + Shaders_SPIRV) + +if (PLATFORM MATCHES "SDL2") + target_compile_definitions(WickedEngine PUBLIC SDL2=1) +endif() diff --git a/WickedEngine/LUA/CMakeLists.txt b/WickedEngine/LUA/CMakeLists.txt new file mode 100644 index 000000000..f0586da52 --- /dev/null +++ b/WickedEngine/LUA/CMakeLists.txt @@ -0,0 +1,36 @@ +add_library(LUA STATIC + lapi.c + lauxlib.c + lbaselib.c + lbitlib.c + lcode.c + lcorolib.c + lctype.c + ldblib.c + ldebug.c + ldo.c + ldump.c + lfunc.c + lgc.c + linit.c + liolib.c + llex.c + lmathlib.c + lmem.c + loadlib.c + lobject.c + lopcodes.c + loslib.c + lparser.c + lstate.c + lstring.c + lstrlib.c + ltable.c + ltablib.c + ltm.c + lua.c + lundump.c + lutf8lib.c + lvm.c + lzio.c +) \ No newline at end of file diff --git a/WickedEngine/MainComponent.cpp b/WickedEngine/MainComponent.cpp index 7695eaa6d..809fa17f6 100644 --- a/WickedEngine/MainComponent.cpp +++ b/WickedEngine/MainComponent.cpp @@ -45,32 +45,62 @@ void MainComponent::Initialize() bool debugdevice = wiStartupArguments::HasArgument("debugdevice"); - if (wiStartupArguments::HasArgument("vulkan")) + bool use_dx11 = wiStartupArguments::HasArgument("dx11"); + bool use_dx12 = wiStartupArguments::HasArgument("dx12"); + bool use_vulkan = wiStartupArguments::HasArgument("vulkan"); + +#ifndef WICKEDENGINE_BUILD_DX11 + if (use_dx11) { + wiHelper::messageBox("DirectX 11 not found during build! DirectX 11 API disabled!", "Error"); + use_dx11 = false; + } +#endif +#ifndef WICKEDENGINE_BUILD_DX12 + if (use_dx12) { + wiHelper::messageBox("DirectX 12 not found during build! DirectX 12 API disabled!", "Error"); + use_dx12 = false; + } +#endif +#ifndef WICKEDENGINE_BUILD_VULKAN + if (use_vulkan) { + wiHelper::messageBox("Vulkan SDK not found during build! Vulkan API disabled!", "Error"); + use_vulkan = false; + } +#endif + + if (!use_dx11 && !use_dx12 && !use_vulkan) + { +#if defined(WICKEDENGINE_BUILD_DX11) + use_dx11 = true; +#elif defined(WICKEDENGINE_BUILD_DX12) + use_dx12 = true; +#elif defined(WICKEDENGINE_BUILD_VULKAN) + use_vulkan = true; +#else + wiBackLog::post("No rendering backend is enabled! Please enable at least one so we can use it as default"); + assert(false); +#endif + } + assert(use_dx11 || use_dx12 || use_vulkan); + + if (use_vulkan) { #ifdef WICKEDENGINE_BUILD_VULKAN wiRenderer::SetShaderPath(wiRenderer::GetShaderPath() + "spirv/"); wiRenderer::SetDevice(std::make_shared(window, fullscreen, debugdevice)); -#else - wiHelper::messageBox("Vulkan SDK not found during build! Vulkan API disabled!", "Error"); #endif } - else if (wiStartupArguments::HasArgument("dx12")) + else if (use_dx12) { #ifdef WICKEDENGINE_BUILD_DX12 wiRenderer::SetShaderPath(wiRenderer::GetShaderPath() + "hlsl6/"); wiRenderer::SetDevice(std::make_shared(window, fullscreen, debugdevice)); -#else - wiHelper::messageBox("DirectX 12 not found during build! DirectX 12 API disabled!", "Error"); #endif } - - // default graphics device: - if (wiRenderer::GetDevice() == nullptr) + else if (use_dx11) { #ifdef WICKEDENGINE_BUILD_DX11 wiRenderer::SetDevice(std::make_shared(window, fullscreen, debugdevice)); -#else - wiHelper::messageBox("DirectX 11 not found during build! DirectX 11 API disabled!", "Error"); #endif } diff --git a/WickedEngine/MainComponent.h b/WickedEngine/MainComponent.h index 5fd8bf536..e0fad7e50 100644 --- a/WickedEngine/MainComponent.h +++ b/WickedEngine/MainComponent.h @@ -10,7 +10,7 @@ class RenderPath; class MainComponent { protected: - RenderPath* activePath; + RenderPath* activePath = nullptr; float targetFrameRate = 60; bool frameskip = true; bool framerate_lock = false; diff --git a/WickedEngine/Utility/CMakeLists.txt b/WickedEngine/Utility/CMakeLists.txt new file mode 100644 index 000000000..309a07bd7 --- /dev/null +++ b/WickedEngine/Utility/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library(Utility STATIC + D3D12MemAlloc.cpp + spirv_cfg.cpp + spirv_cpp.cpp + spirv_cross.cpp + spirv_cross_c.cpp + spirv_cross_parsed_ir.cpp + spirv_cross_util.cpp + spirv_glsl.cpp + spirv_hlsl.cpp + spirv_msl.cpp + spirv_parser.cpp + spirv_reflect.cpp + utility_common.cpp +) \ No newline at end of file diff --git a/WickedEngine/Utility/portable-file-dialogs.h b/WickedEngine/Utility/portable-file-dialogs.h new file mode 100644 index 000000000..f9d9d7e82 --- /dev/null +++ b/WickedEngine/Utility/portable-file-dialogs.h @@ -0,0 +1,1668 @@ +// +// Portable File Dialogs +// +// Copyright © 2018—2020 Sam Hocevar +// +// This library is free software. It comes without any warranty, to +// the extent permitted by applicable law. You can redistribute it +// and/or modify it under the terms of the Do What the Fuck You Want +// to Public License, Version 2, as published by the WTFPL Task Force. +// See http://www.wtfpl.net/ for more details. +// + +#pragma once + +#if _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN 1 +#endif +#include +#include +#include +#include +#include +#include // std::async + +#elif __EMSCRIPTEN__ +#include + +#else +#ifndef _POSIX_C_SOURCE +# define _POSIX_C_SOURCE 2 // for popen() +#endif +#include // popen() +#include // std::getenv() +#include // fcntl() +#include // read(), pipe(), dup2() +#include // ::kill, std::signal +#include // waitpid() +#endif + +#include // std::string +#include // std::shared_ptr +#include // std::ostream +#include // std::map +#include // std::set +#include // std::regex +#include // std::mutex, std::this_thread +#include // std::chrono + +namespace pfd +{ + +enum class button +{ + cancel = -1, + ok, + yes, + no, + abort, + retry, + ignore, +}; + +enum class choice +{ + ok = 0, + ok_cancel, + yes_no, + yes_no_cancel, + retry_cancel, + abort_retry_ignore, +}; + +enum class icon +{ + info = 0, + warning, + error, + question, +}; + +// Additional option flags for various dialog constructors +enum class opt : uint8_t +{ + none = 0, + // For file open, allow multiselect. + multiselect = 0x1, + // For file save, force overwrite and disable the confirmation dialog. + force_overwrite = 0x2, + // For folder select, force path to be the provided argument instead + // of the last opened directory, which is the Microsoft-recommended, + // user-friendly behaviour. + force_path = 0x4, +}; + +inline opt operator |(opt a, opt b) { return opt(uint8_t(a) | uint8_t(b)); } +inline bool operator &(opt a, opt b) { return bool(uint8_t(a) & uint8_t(b)); } + +// The settings class, only exposing to the user a way to set verbose mode +// and to force a rescan of installed desktop helpers (zenity, kdialog…). +class settings +{ +public: + static bool available(); + + static void verbose(bool value); + static void rescan(); + +protected: + explicit settings(bool resync = false); + + bool check_program(std::string const &program); + + inline bool is_osascript() const; + inline bool is_zenity() const; + inline bool is_kdialog() const; + + enum class flag + { + is_scanned = 0, + is_verbose, + + has_zenity, + has_matedialog, + has_qarma, + has_kdialog, + is_vista, + + max_flag, + }; + + // Static array of flags for internal state + bool const &flags(flag in_flag) const; + + // Non-const getter for the static array of flags + bool &flags(flag in_flag); +}; + +// Internal classes, not to be used by client applications +namespace internal +{ + +// Process wait timeout, in milliseconds +static int const default_wait_timeout = 20; + +class executor +{ + friend class dialog; + +public: + // High level function to get the result of a command + std::string result(int *exit_code = nullptr); + + // High level function to abort + bool kill(); + +#if _WIN32 + void start_func(std::function const &fun); + static BOOL CALLBACK enum_windows_callback(HWND hwnd, LPARAM lParam); +#elif __EMSCRIPTEN__ + void start(int exit_code); +#else + void start_process(std::vector const &command); +#endif + + ~executor(); + +protected: + bool ready(int timeout = default_wait_timeout); + void stop(); + +private: + bool m_running = false; + std::string m_stdout; + int m_exit_code = -1; +#if _WIN32 + std::future m_future; + std::set m_windows; + std::condition_variable m_cond; + std::mutex m_mutex; + DWORD m_tid; +#elif __EMSCRIPTEN__ || __NX__ + // FIXME: do something +#else + pid_t m_pid = 0; + int m_fd = -1; +#endif +}; + +class platform +{ +protected: +#if _WIN32 + // Helper class around LoadLibraryA() and GetProcAddress() with some safety + class dll + { + public: + dll(std::string const &name); + ~dll(); + + template class proc + { + public: + proc(dll const &lib, std::string const &sym) + : m_proc(reinterpret_cast(::GetProcAddress(lib.handle, sym.c_str()))) + {} + + operator bool() const { return m_proc != nullptr; } + operator T *() const { return m_proc; } + + private: + T *m_proc; + }; + + private: + HMODULE handle; + }; + + // Helper class around CreateActCtx() and ActivateActCtx() + class new_style_context + { + public: + new_style_context(); + ~new_style_context(); + + private: + HANDLE create(); + ULONG_PTR m_cookie = 0; + }; +#endif +}; + +class dialog : protected settings, protected platform +{ +public: + bool ready(int timeout = default_wait_timeout); + bool kill(); + +protected: + explicit dialog(); + + std::vector desktop_helper() const; + std::string buttons_to_name(choice _choice) const; + std::string get_icon_name(icon _icon) const; + + std::string powershell_quote(std::string const &str) const; + std::string osascript_quote(std::string const &str) const; + std::string shell_quote(std::string const &str) const; + + // Keep handle to executing command + std::shared_ptr m_async; +}; + +class file_dialog : public dialog +{ +protected: + enum type + { + open, + save, + folder, + }; + + file_dialog(type in_type, + std::string const &title, + std::string const &default_path = "", + std::vector const &filters = {}, + opt options = opt::none); + +protected: + std::string string_result(); + std::vector vector_result(); + +#if _WIN32 + static int CALLBACK bffcallback(HWND hwnd, UINT uMsg, LPARAM, LPARAM pData); + std::string select_folder_vista(IFileDialog *ifd, bool force_path); + + std::wstring m_wtitle; + std::wstring m_wdefault_path; + + std::vector m_vector_result; +#endif +}; + +} // namespace internal + +// +// The notify widget +// + +class notify : public internal::dialog +{ +public: + notify(std::string const &title, + std::string const &message, + icon _icon = icon::info); +}; + +// +// The message widget +// + +class message : public internal::dialog +{ +public: + message(std::string const &title, + std::string const &text, + choice _choice = choice::ok_cancel, + icon _icon = icon::info); + + button result(); + +private: + // Some extra logic to map the exit code to button number + std::map m_mappings; +}; + +// +// The open_file, save_file, and open_folder widgets +// + +class open_file : public internal::file_dialog +{ +public: + open_file(std::string const &title, + std::string const &default_path = "", + std::vector const &filters = { "All Files", "*" }, + opt options = opt::none); + +#if defined(__has_cpp_attribute) + #if __has_cpp_attribute(deprecated) + // Backwards compatibility + [[deprecated("Use pfd::opt::multiselect instead of allow_multiselect")]] +#endif +#endif + open_file(std::string const &title, + std::string const &default_path, + std::vector const &filters, + bool allow_multiselect); + + std::vector result(); +}; + +class save_file : public internal::file_dialog +{ +public: + save_file(std::string const &title, + std::string const &default_path = "", + std::vector const &filters = { "All Files", "*" }, + opt options = opt::none); + +#if defined(__has_cpp_attribute) + #if __has_cpp_attribute(deprecated) + // Backwards compatibility + [[deprecated("Use pfd::opt::force_overwrite instead of confirm_overwrite")]] +#endif +#endif + save_file(std::string const &title, + std::string const &default_path, + std::vector const &filters, + bool confirm_overwrite); + + std::string result(); +}; + +class select_folder : public internal::file_dialog +{ +public: + select_folder(std::string const &title, + std::string const &default_path = "", + opt options = opt::none); + + std::string result(); +}; + +// +// Below this are all the method implementations. You may choose to define the +// macro PFD_SKIP_IMPLEMENTATION everywhere before including this header except +// in one place. This may reduce compilation times. +// + +#if !defined PFD_SKIP_IMPLEMENTATION + +// internal free functions implementations + +namespace internal +{ + +#if _WIN32 +static inline std::wstring str2wstr(std::string const &str) +{ + int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), nullptr, 0); + std::wstring ret(len, '\0'); + MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), (LPWSTR)ret.data(), (int)ret.size()); + return ret; +} + +static inline std::string wstr2str(std::wstring const &str) +{ + int len = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int)str.size(), nullptr, 0, nullptr, nullptr); + std::string ret(len, '\0'); + WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int)str.size(), (LPSTR)ret.data(), (int)ret.size(), nullptr, nullptr); + return ret; +} + +static inline bool is_vista() +{ + OSVERSIONINFOEXW osvi; + memset(&osvi, 0, sizeof(osvi)); + DWORDLONG const mask = VerSetConditionMask( + VerSetConditionMask( + VerSetConditionMask( + 0, VER_MAJORVERSION, VER_GREATER_EQUAL), + VER_MINORVERSION, VER_GREATER_EQUAL), + VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + osvi.dwOSVersionInfoSize = sizeof(osvi); + osvi.dwMajorVersion = HIBYTE(_WIN32_WINNT_VISTA); + osvi.dwMinorVersion = LOBYTE(_WIN32_WINNT_VISTA); + osvi.wServicePackMajor = 0; + + return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, mask) != FALSE; +} +#endif + +// This is necessary until C++20 which will have std::string::ends_with() etc. + +static inline bool ends_with(std::string const &str, std::string const &suffix) +{ + return suffix.size() <= str.size() && + str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; +} + +static inline bool starts_with(std::string const &str, std::string const &prefix) +{ + return prefix.size() <= str.size() && + str.compare(0, prefix.size(), prefix) == 0; +} + +} // namespace internal + +// settings implementation + +inline settings::settings(bool resync) +{ + flags(flag::is_scanned) &= !resync; + + if (flags(flag::is_scanned)) + return; + +#if _WIN32 + flags(flag::is_vista) = internal::is_vista(); +#elif !__APPLE__ + flags(flag::has_zenity) = check_program("zenity"); + flags(flag::has_matedialog) = check_program("matedialog"); + flags(flag::has_qarma) = check_program("qarma"); + flags(flag::has_kdialog) = check_program("kdialog"); + + // If multiple helpers are available, try to default to the best one + if (flags(flag::has_zenity) && flags(flag::has_kdialog)) + { + auto desktop_name = std::getenv("XDG_SESSION_DESKTOP"); + if (desktop_name && desktop_name == std::string("gnome")) + flags(flag::has_kdialog) = false; + else if (desktop_name && desktop_name == std::string("KDE")) + flags(flag::has_zenity) = false; + } +#endif + + flags(flag::is_scanned) = true; +} + +inline bool settings::available() +{ +#if _WIN32 + return true; +#elif __APPLE__ + return true; +#else + settings tmp; + return tmp.flags(flag::has_zenity) || + tmp.flags(flag::has_matedialog) || + tmp.flags(flag::has_qarma) || + tmp.flags(flag::has_kdialog); +#endif +} + +inline void settings::verbose(bool value) +{ + settings().flags(flag::is_verbose) = value; +} + +inline void settings::rescan() +{ + settings(/* resync = */ true); +} + +// Check whether a program is present using “which”. +inline bool settings::check_program(std::string const &program) +{ +#if _WIN32 + (void)program; + return false; +#elif __EMSCRIPTEN__ + (void)program; + return false; +#else + int exit_code = -1; + internal::executor async; + async.start_process({"/bin/sh", "-c", "which " + program}); + async.result(&exit_code); + return exit_code == 0; +#endif +} + +inline bool settings::is_osascript() const +{ +#if __APPLE__ + return true; +#else + return false; +#endif +} + +inline bool settings::is_zenity() const +{ + return flags(flag::has_zenity) || + flags(flag::has_matedialog) || + flags(flag::has_qarma); +} + +inline bool settings::is_kdialog() const +{ + return flags(flag::has_kdialog); +} + +inline bool const &settings::flags(flag in_flag) const +{ + static bool flags[size_t(flag::max_flag)]; + return flags[size_t(in_flag)]; +} + +inline bool &settings::flags(flag in_flag) +{ + return const_cast(static_cast(this)->flags(in_flag)); +} + +// executor implementation + +inline std::string internal::executor::result(int *exit_code /* = nullptr */) +{ + stop(); + if (exit_code) + *exit_code = m_exit_code; + return m_stdout; +} + +inline bool internal::executor::kill() +{ +#if _WIN32 + if (m_future.valid()) + { + // Close all windows that weren’t open when we started the future + auto previous_windows = m_windows; + EnumWindows(&enum_windows_callback, (LPARAM)this); + for (auto hwnd : m_windows) + if (previous_windows.find(hwnd) == previous_windows.end()) + SendMessage(hwnd, WM_CLOSE, 0, 0); + } +#elif __EMSCRIPTEN__ || __NX__ + // FIXME: do something + (void)timeout; + return false; // cannot kill +#else + ::kill(m_pid, SIGKILL); +#endif + stop(); + return true; +} + +#if _WIN32 +inline BOOL CALLBACK internal::executor::enum_windows_callback(HWND hwnd, LPARAM lParam) +{ + auto that = (executor *)lParam; + + DWORD pid; + auto tid = GetWindowThreadProcessId(hwnd, &pid); + if (tid == that->m_tid) + that->m_windows.insert(hwnd); + return TRUE; +} +#endif + +#if _WIN32 +inline void internal::executor::start_func(std::function const &fun) +{ + stop(); + + auto trampoline = [fun, this]() + { + // Save our thread id so that the caller can cancel us + m_tid = GetCurrentThreadId(); + EnumWindows(&enum_windows_callback, (LPARAM)this); + m_cond.notify_all(); + return fun(&m_exit_code); + }; + + std::unique_lock lock(m_mutex); + m_future = std::async(std::launch::async, trampoline); + m_cond.wait(lock); + m_running = true; +} + +#elif __EMSCRIPTEN__ +inline void internal::executor::start(int exit_code) +{ + m_exit_code = exit_code; +} + +#else +inline void internal::executor::start_process(std::vector const &command) +{ + stop(); + m_stdout.clear(); + m_exit_code = -1; + + int in[2], out[2]; + if (pipe(in) != 0 || pipe(out) != 0) + return; + + m_pid = fork(); + if (m_pid < 0) + return; + + close(in[m_pid ? 0 : 1]); + close(out[m_pid ? 1 : 0]); + + if (m_pid == 0) + { + dup2(in[0], STDIN_FILENO); + dup2(out[1], STDOUT_FILENO); + + // Ignore stderr so that it doesn’t pollute the console (e.g. GTK+ errors from zenity) + int fd = open("/dev/null", O_WRONLY); + dup2(fd, STDERR_FILENO); + close(fd); + + std::vector args; + std::transform(command.cbegin(), command.cend(), std::back_inserter(args), + [](std::string const &s) { return const_cast(s.c_str()); }); + args.push_back(nullptr); // null-terminate argv[] + + execvp(args[0], args.data()); + exit(1); + } + + close(in[1]); + m_fd = out[0]; + auto flags = fcntl(m_fd, F_GETFL); + fcntl(m_fd, F_SETFL, flags | O_NONBLOCK); + + m_running = true; +} +#endif + +inline internal::executor::~executor() +{ + stop(); +} + +inline bool internal::executor::ready(int timeout /* = default_wait_timeout */) +{ + if (!m_running) + return true; + +#if _WIN32 + if (m_future.valid()) + { + auto status = m_future.wait_for(std::chrono::milliseconds(timeout)); + if (status != std::future_status::ready) + { + // On Windows, we need to run the message pump. If the async + // thread uses a Windows API dialog, it may be attached to the + // main thread and waiting for messages that only we can dispatch. + MSG msg; + while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + return false; + } + + m_stdout = m_future.get(); + } +#elif __EMSCRIPTEN__ || __NX__ + // FIXME: do something + (void)timeout; +#else + char buf[BUFSIZ]; + ssize_t received = read(m_fd, buf, BUFSIZ); // Flawfinder: ignore + if (received > 0) + { + m_stdout += std::string(buf, received); + return false; + } + + // Reap child process if it is dead. It is possible that the system has already reaped it + // (this happens when the calling application handles or ignores SIG_CHLD) and results in + // waitpid() failing with ECHILD. Otherwise we assume the child is running and we sleep for + // a little while. + int status; + pid_t child = waitpid(m_pid, &status, WNOHANG); + if (child != m_pid && (child >= 0 || errno != ECHILD)) + { + // FIXME: this happens almost always at first iteration + std::this_thread::sleep_for(std::chrono::milliseconds(timeout)); + return false; + } + + close(m_fd); + m_exit_code = WEXITSTATUS(status); +#endif + + m_running = false; + return true; +} + +inline void internal::executor::stop() +{ + // Loop until the user closes the dialog + while (!ready()) + ; +} + +// dll implementation + +#if _WIN32 +inline internal::platform::dll::dll(std::string const &name) + : handle(::LoadLibraryA(name.c_str())) +{} + +inline internal::platform::dll::~dll() +{ + if (handle) + ::FreeLibrary(handle); +} +#endif // _WIN32 + +// new_style_context implementation + +#if _WIN32 +inline internal::platform::new_style_context::new_style_context() +{ + // Only create one activation context for the whole app lifetime. + static HANDLE hctx = create(); + + if (hctx != INVALID_HANDLE_VALUE) + ActivateActCtx(hctx, &m_cookie); +} + +inline internal::platform::new_style_context::~new_style_context() +{ + DeactivateActCtx(0, m_cookie); +} + +inline HANDLE internal::platform::new_style_context::create() +{ + // This “hack” seems to be necessary for this code to work on windows XP. + // Without it, dialogs do not show and close immediately. GetError() + // returns 0 so I don’t know what causes this. I was not able to reproduce + // this behavior on Windows 7 and 10 but just in case, let it be here for + // those versions too. + // This hack is not required if other dialogs are used (they load comdlg32 + // automatically), only if message boxes are used. + dll comdlg32("comdlg32.dll"); + + // Using approach as shown here: https://stackoverflow.com/a/10444161 + UINT len = ::GetSystemDirectoryA(nullptr, 0); + std::string sys_dir(len, '\0'); + ::GetSystemDirectoryA(&sys_dir[0], len); + + ACTCTXA act_ctx = + { + // Do not set flag ACTCTX_FLAG_SET_PROCESS_DEFAULT, since it causes a + // crash with error “default context is already set”. + sizeof(act_ctx), + ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID, + "shell32.dll", 0, 0, sys_dir.c_str(), (LPCSTR)124, + }; + + return ::CreateActCtxA(&act_ctx); +} +#endif // _WIN32 + +// dialog implementation + +inline bool internal::dialog::ready(int timeout /* = default_wait_timeout */) +{ + return m_async->ready(timeout); +} + +inline bool internal::dialog::kill() +{ + return m_async->kill(); +} + +inline internal::dialog::dialog() + : m_async(std::make_shared()) +{ +} + +inline std::vector internal::dialog::desktop_helper() const +{ +#if __APPLE__ + return { "osascript" }; +#else + return { flags(flag::has_zenity) ? "zenity" + : flags(flag::has_matedialog) ? "matedialog" + : flags(flag::has_qarma) ? "qarma" + : flags(flag::has_kdialog) ? "kdialog" + : "echo" }; +#endif +} + +inline std::string internal::dialog::buttons_to_name(choice _choice) const +{ + switch (_choice) + { + case choice::ok_cancel: return "okcancel"; + case choice::yes_no: return "yesno"; + case choice::yes_no_cancel: return "yesnocancel"; + case choice::retry_cancel: return "retrycancel"; + case choice::abort_retry_ignore: return "abortretryignore"; + /* case choice::ok: */ default: return "ok"; + } +} + +inline std::string internal::dialog::get_icon_name(icon _icon) const +{ + switch (_icon) + { + case icon::warning: return "warning"; + case icon::error: return "error"; + case icon::question: return "question"; + // Zenity wants "information" but WinForms wants "info" + /* case icon::info: */ default: +#if _WIN32 + return "info"; +#else + return "information"; +#endif + } +} + +// THis is only used for debugging purposes +inline std::ostream& operator <<(std::ostream &s, std::vector const &v) +{ + int not_first = 0; + for (auto &e : v) + s << (not_first++ ? " " : "") << e; + return s; +} + +// Properly quote a string for Powershell: replace ' or " with '' or "" +// FIXME: we should probably get rid of newlines! +// FIXME: the \" sequence seems unsafe, too! +// XXX: this is no longer used but I would like to keep it around just in case +inline std::string internal::dialog::powershell_quote(std::string const &str) const +{ + return "'" + std::regex_replace(str, std::regex("['\"]"), "$&$&") + "'"; +} + +// Properly quote a string for osascript: replace \ or " with \\ or \" +// XXX: this also used to replace ' with \' when popen was used, but it would be +// smarter to do shell_quote(osascript_quote(...)) if this is needed again. +inline std::string internal::dialog::osascript_quote(std::string const &str) const +{ + return "\"" + std::regex_replace(str, std::regex("[\\\\\"]"), "\\$&") + "\""; +} + +// Properly quote a string for the shell: just replace ' with '\'' +// XXX: this is no longer used but I would like to keep it around just in case +inline std::string internal::dialog::shell_quote(std::string const &str) const +{ + return "'" + std::regex_replace(str, std::regex("'"), "'\\''") + "'"; +} + +// file_dialog implementation + +inline internal::file_dialog::file_dialog(type in_type, + std::string const &title, + std::string const &default_path /* = "" */, + std::vector const &filters /* = {} */, + opt options /* = opt::none */) +{ +#if _WIN32 + std::string filter_list; + std::regex whitespace(" *"); + for (size_t i = 0; i + 1 < filters.size(); i += 2) + { + filter_list += filters[i] + '\0'; + filter_list += std::regex_replace(filters[i + 1], whitespace, ";") + '\0'; + } + filter_list += '\0'; + + m_async->start_func([this, in_type, title, default_path, filter_list, + options](int *exit_code) -> std::string + { + (void)exit_code; + m_wtitle = internal::str2wstr(title); + m_wdefault_path = internal::str2wstr(default_path); + auto wfilter_list = internal::str2wstr(filter_list); + + // Folder selection uses a different method + if (in_type == type::folder) + { + dll ole32("ole32.dll"); + + auto status = dll::proc(ole32, "CoInitializeEx") + (nullptr, COINIT_APARTMENTTHREADED); + if (flags(flag::is_vista)) + { + // On Vista and higher we should be able to use IFileDialog for folder selection + IFileDialog *ifd; + HRESULT hr = dll::proc(ole32, "CoCreateInstance") + (CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&ifd)); + + // In case CoCreateInstance fails (which it should not), try legacy approach + if (SUCCEEDED(hr)) + return select_folder_vista(ifd, options & opt::force_path); + } + + BROWSEINFOW bi; + memset(&bi, 0, sizeof(bi)); + + bi.lpfn = &bffcallback; + bi.lParam = (LPARAM)this; + + if (flags(flag::is_vista)) + { + // This hangs on Windows XP, as reported here: + // https://github.com/samhocevar/portable-file-dialogs/pull/21 + if (status == S_OK) + bi.ulFlags |= BIF_NEWDIALOGSTYLE; + bi.ulFlags |= BIF_EDITBOX; + bi.ulFlags |= BIF_STATUSTEXT; + } + + auto *list = SHBrowseForFolderW(&bi); + std::string ret; + if (list) + { + auto buffer = new wchar_t[MAX_PATH]; + SHGetPathFromIDListW(list, buffer); + dll::proc(ole32, "CoTaskMemFree")(list); + ret = internal::wstr2str(buffer); + delete[] buffer; + } + if (status == S_OK) + dll::proc(ole32, "CoUninitialize")(); + return ret; + } + + OPENFILENAMEW ofn; + memset(&ofn, 0, sizeof(ofn)); + ofn.lStructSize = sizeof(OPENFILENAMEW); + ofn.hwndOwner = GetActiveWindow(); + + ofn.lpstrFilter = wfilter_list.c_str(); + + auto woutput = std::wstring(MAX_PATH * 256, L'\0'); + ofn.lpstrFile = (LPWSTR)woutput.data(); + ofn.nMaxFile = (DWORD)woutput.size(); + if (!m_wdefault_path.empty()) + { + // If a directory was provided, use it as the initial directory. If + // a valid path was provided, use it as the initial file. Otherwise, + // let the Windows API decide. + auto path_attr = GetFileAttributesW(m_wdefault_path.c_str()); + if (path_attr != INVALID_FILE_ATTRIBUTES && (path_attr & FILE_ATTRIBUTE_DIRECTORY)) + ofn.lpstrInitialDir = m_wdefault_path.c_str(); + else if (m_wdefault_path.size() <= woutput.size()) + //second argument is size of buffer, not length of string + StringCchCopyW(ofn.lpstrFile, MAX_PATH*256+1, m_wdefault_path.c_str()); + else + { + ofn.lpstrFileTitle = (LPWSTR)m_wdefault_path.data(); + ofn.nMaxFileTitle = (DWORD)m_wdefault_path.size(); + } + } + ofn.lpstrTitle = m_wtitle.c_str(); + ofn.Flags = OFN_NOCHANGEDIR | OFN_EXPLORER; + + dll comdlg32("comdlg32.dll"); + + if (in_type == type::save) + { + if (!(options & opt::force_overwrite)) + ofn.Flags |= OFN_OVERWRITEPROMPT; + + // using set context to apply new visual style (required for windows XP) + new_style_context ctx; + + dll::proc get_save_file_name(comdlg32, "GetSaveFileNameW"); + if (get_save_file_name(&ofn) == 0) + return ""; + return internal::wstr2str(woutput.c_str()); + } + + if (options & opt::multiselect) + ofn.Flags |= OFN_ALLOWMULTISELECT; + ofn.Flags |= OFN_PATHMUSTEXIST; + + // using set context to apply new visual style (required for windows XP) + new_style_context ctx; + + dll::proc get_open_file_name(comdlg32, "GetOpenFileNameW"); + if (get_open_file_name(&ofn) == 0) + return ""; + + std::string prefix; + for (wchar_t const *p = woutput.c_str(); *p; ) + { + auto filename = internal::wstr2str(p); + p += filename.size(); + // In multiselect mode, we advance p one step more and + // check for another filename. If there is one and the + // prefix is empty, it means we just read the prefix. + if ((options & opt::multiselect) && *++p && prefix.empty()) + { + prefix = filename + "/"; + continue; + } + + m_vector_result.push_back(prefix + filename); + } + + return ""; + }); +#else + auto command = desktop_helper(); + + if (is_osascript()) + { + std::string script = "set ret to choose"; + switch (in_type) + { + case type::save: + script += " file name"; + break; + case type::open: default: + script += " file"; + if (options & opt::multiselect) + script += " with multiple selections allowed"; + break; + case type::folder: + script += " folder"; + break; + } + + if (default_path.size()) + script += " default location " + osascript_quote(default_path); + script += " with prompt " + osascript_quote(title); + + if (in_type == type::open) + { + // Concatenate all user-provided filter patterns + std::string patterns; + for (size_t i = 0; i < filters.size() / 2; ++i) + patterns += " " + filters[2 * i + 1]; + + // Split the pattern list to check whether "*" is in there; if it + // is, we have to disable filters because there is no mechanism in + // OS X for the user to override the filter. + std::regex sep("\\s+"); + std::string filter_list; + bool has_filter = true; + std::sregex_token_iterator iter(patterns.begin(), patterns.end(), sep, -1); + std::sregex_token_iterator end; + for ( ; iter != end; ++iter) + { + auto pat = iter->str(); + if (pat == "*" || pat == "*.*") + has_filter = false; + else if (internal::starts_with(pat, "*.")) + filter_list += (filter_list.size() == 0 ? "" : ",") + + osascript_quote(pat.substr(2, pat.size() - 2)); + } + if (has_filter && filter_list.size() > 0) + script += " of type {" + filter_list + "}"; + } + + if (in_type == type::open && (options & opt::multiselect)) + { + script += "\nset s to \"\""; + script += "\nrepeat with i in ret"; + script += "\n set s to s & (POSIX path of i) & \"\\n\""; + script += "\nend repeat"; + script += "\ncopy s to stdout"; + } + else + { + script += "\nPOSIX path of ret"; + } + + command.push_back("-e"); + command.push_back(script); + } + else if (is_zenity()) + { + command.push_back("--file-selection"); + command.push_back("--filename=" + default_path); + command.push_back("--title"); + command.push_back(title); + command.push_back("--separator=\n"); + + for (size_t i = 0; i < filters.size() / 2; ++i) + { + command.push_back("--file-filter"); + command.push_back(filters[2 * i] + "|" + filters[2 * i + 1]); + } + + if (in_type == type::save) + command.push_back("--save"); + if (in_type == type::folder) + command.push_back("--directory"); + if (!(options & opt::force_overwrite)) + command.push_back("--confirm-overwrite"); + if (options & opt::multiselect) + command.push_back("--multiple"); + } + else if (is_kdialog()) + { + switch (in_type) + { + case type::save: command.push_back("--getsavefilename"); break; + case type::open: command.push_back("--getopenfilename"); break; + case type::folder: command.push_back("--getexistingdirectory"); break; + } + if (options & opt::multiselect) + command.push_back(" --multiple"); + + command.push_back(default_path); + + std::string filter; + for (size_t i = 0; i < filters.size() / 2; ++i) + filter += (i == 0 ? "" : " | ") + filters[2 * i] + "(" + filters[2 * i + 1] + ")"; + command.push_back(filter); + + command.push_back("--title"); + command.push_back(title); + } + + if (flags(flag::is_verbose)) + std::cerr << "pfd: " << command << std::endl; + + m_async->start_process(command); +#endif +} + +inline std::string internal::file_dialog::string_result() +{ +#if _WIN32 + return m_async->result(); +#else + // Strip the newline character + auto ret = m_async->result(); + return ret.back() == '\n' ? ret.substr(0, ret.size() - 1) : ret; +#endif +} + +inline std::vector internal::file_dialog::vector_result() +{ +#if _WIN32 + m_async->result(); + return m_vector_result; +#else + std::vector ret; + auto result = m_async->result(); + for (;;) + { + // Split result along newline characters + auto i = result.find('\n'); + if (i == 0 || i == std::string::npos) + break; + ret.push_back(result.substr(0, i)); + result = result.substr(i + 1, result.size()); + } + return ret; +#endif +} + +#if _WIN32 +// Use a static function to pass as BFFCALLBACK for legacy folder select +inline int CALLBACK internal::file_dialog::bffcallback(HWND hwnd, UINT uMsg, + LPARAM, LPARAM pData) +{ + auto inst = (file_dialog *)pData; + switch (uMsg) + { + case BFFM_INITIALIZED: + SendMessage(hwnd, BFFM_SETSELECTIONW, TRUE, (LPARAM)inst->m_wdefault_path.c_str()); + break; + } + return 0; +} + +inline std::string internal::file_dialog::select_folder_vista(IFileDialog *ifd, bool force_path) +{ + std::string result; + + IShellItem *folder; + + // Load library at runtime so app doesn't link it at load time (which will fail on windows XP) + dll shell32("shell32.dll"); + dll::proc + create_item(shell32, "SHCreateItemFromParsingName"); + + if (!create_item) + return ""; + + auto hr = create_item(m_wdefault_path.c_str(), + nullptr, + IID_PPV_ARGS(&folder)); + + // Set default folder if found. This only sets the default folder. If + // Windows has any info about the most recently selected folder, it + // will display it instead. Generally, calling SetFolder() to set the + // current directory “is not a good or expected user experience and + // should therefore be avoided”: + // https://docs.microsoft.com/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-setfolder + if (SUCCEEDED(hr)) + { + if (force_path) + ifd->SetFolder(folder); + else + ifd->SetDefaultFolder(folder); + folder->Release(); + } + + // Set the dialog title and option to select folders + ifd->SetOptions(FOS_PICKFOLDERS); + ifd->SetTitle(m_wtitle.c_str()); + + hr = ifd->Show(GetActiveWindow()); + if (SUCCEEDED(hr)) + { + IShellItem* item; + hr = ifd->GetResult(&item); + if (SUCCEEDED(hr)) + { + wchar_t* wselected = nullptr; + item->GetDisplayName(SIGDN_FILESYSPATH, &wselected); + item->Release(); + + if (wselected) + { + result = internal::wstr2str(std::wstring(wselected)); + dll ole32("ole32.dll"); + dll::proc(ole32, "CoTaskMemFree")(wselected); + } + } + } + + ifd->Release(); + + return result; +} +#endif + +// notify implementation + +inline notify::notify(std::string const &title, + std::string const &message, + icon _icon /* = icon::info */) +{ + if (_icon == icon::question) // Not supported by notifications + _icon = icon::info; + +#if _WIN32 + // Use a static shared pointer for notify_icon so that we can delete + // it whenever we need to display a new one, and we can also wait + // until the program has finished running. + struct notify_icon_data : public NOTIFYICONDATAW + { + ~notify_icon_data() { Shell_NotifyIconW(NIM_DELETE, this); } + }; + + static std::shared_ptr nid; + + // Release the previous notification icon, if any, and allocate a new + // one. Note that std::make_shared() does value initialization, so there + // is no need to memset the structure. + nid = nullptr; + nid = std::make_shared(); + + // For XP support + nid->cbSize = NOTIFYICONDATAW_V2_SIZE; + nid->hWnd = nullptr; + nid->uID = 0; + + // Flag Description: + // - NIF_ICON The hIcon member is valid. + // - NIF_MESSAGE The uCallbackMessage member is valid. + // - NIF_TIP The szTip member is valid. + // - NIF_STATE The dwState and dwStateMask members are valid. + // - NIF_INFO Use a balloon ToolTip instead of a standard ToolTip. The szInfo, uTimeout, szInfoTitle, and dwInfoFlags members are valid. + // - NIF_GUID Reserved. + nid->uFlags = NIF_MESSAGE | NIF_ICON | NIF_INFO; + + // Flag Description + // - NIIF_ERROR An error icon. + // - NIIF_INFO An information icon. + // - NIIF_NONE No icon. + // - NIIF_WARNING A warning icon. + // - NIIF_ICON_MASK Version 6.0. Reserved. + // - NIIF_NOSOUND Version 6.0. Do not play the associated sound. Applies only to balloon ToolTips + switch (_icon) + { + case icon::warning: nid->dwInfoFlags = NIIF_WARNING; break; + case icon::error: nid->dwInfoFlags = NIIF_ERROR; break; + /* case icon::info: */ default: nid->dwInfoFlags = NIIF_INFO; break; + } + + ENUMRESNAMEPROC icon_enum_callback = [](HMODULE, LPCTSTR, LPTSTR lpName, LONG_PTR lParam) -> BOOL + { + ((NOTIFYICONDATAW *)lParam)->hIcon = ::LoadIcon(GetModuleHandle(nullptr), lpName); + return false; + }; + + nid->hIcon = ::LoadIcon(nullptr, IDI_APPLICATION); + ::EnumResourceNames(nullptr, RT_GROUP_ICON, icon_enum_callback, (LONG_PTR)nid.get()); + + nid->uTimeout = 5000; + + StringCchCopyW(nid->szInfoTitle, ARRAYSIZE(nid->szInfoTitle), internal::str2wstr(title).c_str()); + StringCchCopyW(nid->szInfo, ARRAYSIZE(nid->szInfo), internal::str2wstr(message).c_str()); + + // Display the new icon + Shell_NotifyIconW(NIM_ADD, nid.get()); +#else + auto command = desktop_helper(); + + if (is_osascript()) + { + command.push_back("-e"); + command.push_back("display notification " + osascript_quote(message) + + " with title " + osascript_quote(title)); + } + else if (is_zenity()) + { + command.push_back("--notification"); + command.push_back("--window-icon"); + command.push_back(get_icon_name(_icon)); + command.push_back("--text"); + command.push_back(title + "\n" + message); + } + else if (is_kdialog()) + { + command.push_back("--icon"); + command.push_back(get_icon_name(_icon)); + command.push_back("--title"); + command.push_back(title); + command.push_back("--passivepopup"); + command.push_back(message); + command.push_back("5"); + } + + if (flags(flag::is_verbose)) + std::cerr << "pfd: " << command << std::endl; + + m_async->start_process(command); +#endif +} + +// message implementation + +inline message::message(std::string const &title, + std::string const &text, + choice _choice /* = choice::ok_cancel */, + icon _icon /* = icon::info */) +{ +#if _WIN32 + UINT style = MB_TOPMOST; + switch (_icon) + { + case icon::warning: style |= MB_ICONWARNING; break; + case icon::error: style |= MB_ICONERROR; break; + case icon::question: style |= MB_ICONQUESTION; break; + /* case icon::info: */ default: style |= MB_ICONINFORMATION; break; + } + + switch (_choice) + { + case choice::ok_cancel: style |= MB_OKCANCEL; break; + case choice::yes_no: style |= MB_YESNO; break; + case choice::yes_no_cancel: style |= MB_YESNOCANCEL; break; + case choice::retry_cancel: style |= MB_RETRYCANCEL; break; + case choice::abort_retry_ignore: style |= MB_ABORTRETRYIGNORE; break; + /* case choice::ok: */ default: style |= MB_OK; break; + } + + m_mappings[IDCANCEL] = button::cancel; + m_mappings[IDOK] = button::ok; + m_mappings[IDYES] = button::yes; + m_mappings[IDNO] = button::no; + m_mappings[IDABORT] = button::abort; + m_mappings[IDRETRY] = button::retry; + m_mappings[IDIGNORE] = button::ignore; + + m_async->start_func([this, text, title, style](int* exit_code) -> std::string + { + auto wtext = internal::str2wstr(text); + auto wtitle = internal::str2wstr(title); + // using set context to apply new visual style (required for all windows versions) + new_style_context ctx; + *exit_code = MessageBoxW(GetActiveWindow(), wtext.c_str(), wtitle.c_str(), style); + return ""; + }); + +#elif __EMSCRIPTEN__ + std::string full_message; + switch (_icon) + { + case icon::warning: full_message = "⚠️"; break; + case icon::error: full_message = "⛔"; break; + case icon::question: full_message = "❓"; break; + /* case icon::info: */ default: full_message = "ℹ"; break; + } + + full_message += ' ' + title + "\n\n" + text; + + // This does not really start an async task; it just passes the + // EM_ASM_INT return value to a fake start() function. + m_async->start(EM_ASM_INT( + { + if ($1) + return window.confirm(UTF8ToString($0)) ? 0 : -1; + alert(UTF8ToString($0)); + return 0; + }, full_message.c_str(), _choice == choice::ok_cancel)); +#else + auto command = desktop_helper(); + + if (is_osascript()) + { + std::string script = "display dialog " + osascript_quote(text) + + " with title " + osascript_quote(title); + switch (_choice) + { + case choice::ok_cancel: + script += "buttons {\"OK\", \"Cancel\"}" + " default button \"OK\"" + " cancel button \"Cancel\""; + m_mappings[256] = button::cancel; + break; + case choice::yes_no: + script += "buttons {\"Yes\", \"No\"}" + " default button \"Yes\"" + " cancel button \"No\""; + m_mappings[256] = button::no; + break; + case choice::yes_no_cancel: + script += "buttons {\"Yes\", \"No\", \"Cancel\"}" + " default button \"Yes\"" + " cancel button \"Cancel\""; + m_mappings[256] = button::cancel; + break; + case choice::retry_cancel: + script += "buttons {\"Retry\", \"Cancel\"}" + " default button \"Retry\"" + " cancel button \"Cancel\""; + m_mappings[256] = button::cancel; + break; + case choice::abort_retry_ignore: + script += "buttons {\"Abort\", \"Retry\", \"Ignore\"}" + " default button \"Retry\"" + " cancel button \"Retry\""; + m_mappings[256] = button::cancel; + break; + case choice::ok: default: + script += "buttons {\"OK\"}" + " default button \"OK\"" + " cancel button \"OK\""; + m_mappings[256] = button::ok; + break; + } + script += " with icon "; + switch (_icon) + { +#define PFD_OSX_ICON(n) "alias ((path to library folder from system domain) as text " \ + "& \"CoreServices:CoreTypes.bundle:Contents:Resources:" n ".icns\")" + case icon::info: default: script += PFD_OSX_ICON("ToolBarInfo"); break; + case icon::warning: script += "caution"; break; + case icon::error: script += "stop"; break; + case icon::question: script += PFD_OSX_ICON("GenericQuestionMarkIcon"); break; +#undef PFD_OSX_ICON + } + + command.push_back("-e"); + command.push_back(script); + } + else if (is_zenity()) + { + switch (_choice) + { + case choice::ok_cancel: + command.insert(command.end(), { "--question", "--cancel-label=Cancel", "--ok-label=OK" }); break; + case choice::yes_no: + // Do not use standard --question because it causes “No” to return -1, + // which is inconsistent with the “Yes/No/Cancel” mode below. + command.insert(command.end(), { "--question", "--switch", "--extra-button=No", "--extra-button=Yes" }); break; + case choice::yes_no_cancel: + command.insert(command.end(), { "--question", "--switch", "--extra-button=Cancel", "--extra-button=No", "--extra-button=Yes" }); break; + case choice::retry_cancel: + command.insert(command.end(), { "--question", "--switch", "--extra-button=Cancel", "--extra-button=Retry" }); break; + case choice::abort_retry_ignore: + command.insert(command.end(), { "--question", "--switch", "--extra-button=Ignore", "--extra-button=Abort", "--extra-button=Retry" }); break; + case choice::ok: + default: + switch (_icon) + { + case icon::error: command.push_back("--error"); break; + case icon::warning: command.push_back("--warning"); break; + default: command.push_back("--info"); break; + } + } + + command.insert(command.end(), { "--title", title, + "--width=300", "--height=0", // sensible defaults + "--text", text, + "--icon-name=dialog-" + get_icon_name(_icon) }); + } + else if (is_kdialog()) + { + if (_choice == choice::ok) + { + switch (_icon) + { + case icon::error: command.push_back("--error"); break; + case icon::warning: command.push_back("--sorry"); break; + default: command.push_back("--msgbox"); break; + } + } + else + { + std::string flag = "--"; + if (_icon == icon::warning || _icon == icon::error) + flag += "warning"; + flag += "yesno"; + if (_choice == choice::yes_no_cancel) + flag += "cancel"; + command.push_back(flag); + if (_choice == choice::yes_no || _choice == choice::yes_no_cancel) + { + m_mappings[0] = button::yes; + m_mappings[256] = button::no; + } + } + + command.push_back(text); + command.push_back("--title"); + command.push_back(title); + + // Must be after the above part + if (_choice == choice::ok_cancel) + command.insert(command.end(), { "--yes-label", "OK", "--no-label", "Cancel" }); + } + + if (flags(flag::is_verbose)) + std::cerr << "pfd: " << command << std::endl; + + m_async->start_process(command); +#endif +} + +inline button message::result() +{ + int exit_code; + auto ret = m_async->result(&exit_code); + // osascript will say "button returned:Cancel\n" + // and others will just say "Cancel\n" + if (exit_code < 0 || // this means cancel + internal::ends_with(ret, "Cancel\n")) + return button::cancel; + if (internal::ends_with(ret, "OK\n")) + return button::ok; + if (internal::ends_with(ret, "Yes\n")) + return button::yes; + if (internal::ends_with(ret, "No\n")) + return button::no; + if (internal::ends_with(ret, "Abort\n")) + return button::abort; + if (internal::ends_with(ret, "Retry\n")) + return button::retry; + if (internal::ends_with(ret, "Ignore\n")) + return button::ignore; + if (m_mappings.count(exit_code) != 0) + return m_mappings[exit_code]; + return exit_code == 0 ? button::ok : button::cancel; +} + +// open_file implementation + +inline open_file::open_file(std::string const &title, + std::string const &default_path /* = "" */, + std::vector const &filters /* = { "All Files", "*" } */, + opt options /* = opt::none */) + : file_dialog(type::open, title, default_path, filters, options) +{ +} + +inline open_file::open_file(std::string const &title, + std::string const &default_path, + std::vector const &filters, + bool allow_multiselect) + : open_file(title, default_path, filters, + (allow_multiselect ? opt::multiselect : opt::none)) +{ +} + +inline std::vector open_file::result() +{ + return vector_result(); +} + +// save_file implementation + +inline save_file::save_file(std::string const &title, + std::string const &default_path /* = "" */, + std::vector const &filters /* = { "All Files", "*" } */, + opt options /* = opt::none */) + : file_dialog(type::save, title, default_path, filters, options) +{ +} + +inline save_file::save_file(std::string const &title, + std::string const &default_path, + std::vector const &filters, + bool confirm_overwrite) + : save_file(title, default_path, filters, + (confirm_overwrite ? opt::none : opt::force_overwrite)) +{ +} + +inline std::string save_file::result() +{ + return string_result(); +} + +// select_folder implementation + +inline select_folder::select_folder(std::string const &title, + std::string const &default_path /* = "" */, + opt options /* = opt::none */) + : file_dialog(type::folder, title, default_path, {}, options) +{ +} + +inline std::string select_folder::result() +{ + return string_result(); +} + +#endif // PFD_SKIP_IMPLEMENTATION + +} // namespace pfd + diff --git a/WickedEngine/WickedEngine_SOURCE.vcxitems b/WickedEngine/WickedEngine_SOURCE.vcxitems index d7213d485..b214086ee 100644 --- a/WickedEngine/WickedEngine_SOURCE.vcxitems +++ b/WickedEngine/WickedEngine_SOURCE.vcxitems @@ -351,6 +351,7 @@ + @@ -687,6 +688,7 @@ + diff --git a/WickedEngine/WickedEngine_SOURCE.vcxitems.filters b/WickedEngine/WickedEngine_SOURCE.vcxitems.filters index 76478e30c..c5ffd5c21 100644 --- a/WickedEngine/WickedEngine_SOURCE.vcxitems.filters +++ b/WickedEngine/WickedEngine_SOURCE.vcxitems.filters @@ -1194,6 +1194,9 @@ ENGINE\System + + ENGINE\Input + @@ -1988,6 +1991,9 @@ ENGINE\System + + ENGINE\Input + diff --git a/WickedEngine/cullingShaderHF.hlsli b/WickedEngine/cullingShaderHF.hlsli index 7e8e21762..f1b7b6688 100644 --- a/WickedEngine/cullingShaderHF.hlsli +++ b/WickedEngine/cullingShaderHF.hlsli @@ -26,10 +26,10 @@ Plane ComputePlane(float3 p0, float3 p1, float3 p2) } // Four planes of a view frustum (in view space). // The planes are: -// * Left, -// * Right, -// * Top, -// * Bottom. +// * Left, +// * Right, +// * Top, +// * Bottom. // The back and/or front planes can be computed from depth values in the // light culling compute shader. struct Frustum diff --git a/WickedEngine/depthoffieldHF.hlsli b/WickedEngine/depthOfFieldHF.hlsli similarity index 100% rename from WickedEngine/depthoffieldHF.hlsli rename to WickedEngine/depthOfFieldHF.hlsli diff --git a/WickedEngine/depthoffield_mainCS.hlsl b/WickedEngine/depthoffield_mainCS.hlsl index 50e65e117..3cb5b0fb1 100644 --- a/WickedEngine/depthoffield_mainCS.hlsl +++ b/WickedEngine/depthoffield_mainCS.hlsl @@ -1,6 +1,6 @@ #include "globals.hlsli" #include "ShaderInterop_Postprocess.h" -#include "depthoffieldHF.hlsli" +#include "depthOfFieldHF.hlsli" // Implementation based on Jorge Jimenez Siggraph 2014 Next Generation Post Processing in Call of Duty Advanced Warfare diff --git a/WickedEngine/depthoffield_prepassCS.hlsl b/WickedEngine/depthoffield_prepassCS.hlsl index ec51b7db7..26c6b9ccb 100644 --- a/WickedEngine/depthoffield_prepassCS.hlsl +++ b/WickedEngine/depthoffield_prepassCS.hlsl @@ -1,6 +1,6 @@ #include "globals.hlsli" #include "ShaderInterop_Postprocess.h" -#include "depthoffieldHF.hlsli" +#include "depthOfFieldHF.hlsli" TEXTURE2D(input, float4, TEXSLOT_ONDEMAND0); TEXTURE2D(neighborhood_mindepth_maxcoc, float2, TEXSLOT_ONDEMAND1); diff --git a/WickedEngine/depthoffield_upsampleCS.hlsl b/WickedEngine/depthoffield_upsampleCS.hlsl index 3b3cb0fc0..782425461 100644 --- a/WickedEngine/depthoffield_upsampleCS.hlsl +++ b/WickedEngine/depthoffield_upsampleCS.hlsl @@ -1,6 +1,6 @@ #include "globals.hlsli" #include "ShaderInterop_Postprocess.h" -#include "depthoffieldHF.hlsli" +#include "depthOfFieldHF.hlsli" TEXTURE2D(input, float4, TEXSLOT_ONDEMAND0); TEXTURE2D(texture_postfilter, float3, TEXSLOT_ONDEMAND1); diff --git a/WickedEngine/lineardepthCS.hlsl b/WickedEngine/lineardepthCS.hlsl index ab42bd65d..e05a61824 100644 --- a/WickedEngine/lineardepthCS.hlsl +++ b/WickedEngine/lineardepthCS.hlsl @@ -1,6 +1,6 @@ #include "globals.hlsli" #include "ShaderInterop_Renderer.h" -#include "ShaderInterop_PostProcess.h" +#include "ShaderInterop_Postprocess.h" TEXTURE2D(input, float, TEXSLOT_ONDEMAND0); diff --git a/WickedEngine/sdl2.h b/WickedEngine/sdl2.h new file mode 100644 index 000000000..18ab9f2f0 --- /dev/null +++ b/WickedEngine/sdl2.h @@ -0,0 +1,105 @@ +// From https://github.com/xyproto/sdl2-examples/blob/master/include/sdl2.h +// MIT License + +#pragma once + +#include +#include +#include + +namespace sdl2 { + +// Very useful function from Eric Scott Barr: +// https://eb2.co/blog/2014/04/c-plus-plus-14-and-sdl2-managing-resources/ +template +auto make_resource(Creator c, Destructor d, Arguments&&... args) +{ + using std::decay_t; + using std::forward; + using std::unique_ptr; + + auto r = c(forward(args)...); + return unique_ptr, decltype(d)>(r, d); +} + +// The "internal type" of the SDL System +using SDL_System = int; + +// SDL_CreateSDL initiates the use of SDL. +// The given flags are passed to SDL_Init. +// The returned value contains the exit code. +inline SDL_System* SDL_CreateSDL(Uint32 flags) +{ + auto init_status = new SDL_System; + *init_status = SDL_Init(flags); + return init_status; +} + +// SDL_DestroySDL ends the use of SDL +inline void SDL_DestroySDL(SDL_System* init_status) +{ + delete init_status; // Delete the int that contains the return value from SDL_Init + SDL_Quit(); +} + +using sdlsystem_ptr_t = std::unique_ptr; +using window_ptr_t = std::unique_ptr; +using renderer_ptr_t = std::unique_ptr; +using surf_ptr_t = std::unique_ptr; +using texture_ptr_t = std::unique_ptr; + +// Initialize SDL (the returned int* contains the return value from SDL_Init) +inline sdlsystem_ptr_t make_sdlsystem(Uint32 flags) +{ + return make_resource(SDL_CreateSDL, SDL_DestroySDL, flags); +} + +// Create a window (that contains both a SDL_Window and the destructor for SDL_Windows) +inline window_ptr_t make_window(const char* title, int x, int y, int w, int h, Uint32 flags) +{ + return make_resource(SDL_CreateWindow, SDL_DestroyWindow, title, x, y, w, h, flags); +} + +// Create a renderer given a window, containing both the renderer and the destructor +inline renderer_ptr_t make_renderer(SDL_Window* win, int x, Uint32 flags) +{ + return make_resource(SDL_CreateRenderer, SDL_DestroyRenderer, win, x, flags); +} + +// Create a surface from a bmp file, containing both the surface and the destructor +inline surf_ptr_t make_bmp(SDL_RWops* sdlfile) +{ + // May throw an exception if sdlfile is nullptr + return make_resource(SDL_LoadBMP_RW, SDL_FreeSurface, sdlfile, 1); +} + +// Create a texture from a renderer and a surface +inline texture_ptr_t make_texture(SDL_Renderer* ren, SDL_Surface* surf) +{ + return make_resource(SDL_CreateTextureFromSurface, SDL_DestroyTexture, ren, surf); +} + +// Easy to raise exceptions with this class +class SDLError : public std::exception +{ + std::string error_message; + public: + SDLError(const char *err_msg) + { + std::stringstream msg; + msg << err_msg << ": " << SDL_GetError(); + this->error_message = msg.str(); + } + SDLError(const std::string &err_msg) + : SDLError(err_msg.c_str()) + {} + + const char* + what() const noexcept override + { + return error_message.c_str(); + } + +}; + +} // namespace sdl2 diff --git a/WickedEngine/shaders/CMakeLists.txt b/WickedEngine/shaders/CMakeLists.txt new file mode 100644 index 000000000..fef595d1c --- /dev/null +++ b/WickedEngine/shaders/CMakeLists.txt @@ -0,0 +1,376 @@ +# compile shaders + +set(OUTPUT_DIR spirv) + +set(SHADERS_CS + "hairparticle_simulateCS.hlsl" + "emittedparticle_simulateCS.hlsl" + "generateMIPChainCubeCS_float4.hlsl" + "generateMIPChainCubeCS_unorm4.hlsl" + "generateMIPChainCubeArrayCS_float4.hlsl" + "generateMIPChainCubeArrayCS_unorm4.hlsl" + "generateMIPChain3DCS_float4.hlsl" + "generateMIPChain3DCS_unorm4.hlsl" + "generateMIPChain2DCS_float4.hlsl" + "generateMIPChain2DCS_unorm4.hlsl" + "blur_gaussian_float4CS.hlsl" + "bloomseparateCS.hlsl" + "depthoffield_mainCS.hlsl" + "depthoffield_neighborhoodMaxCOCCS.hlsl" + "depthoffield_prepassCS.hlsl" + "depthoffield_upsampleCS.hlsl" + "depthoffield_tileMaxCOC_verticalCS.hlsl" + "depthoffield_tileMaxCOC_horizontalCS.hlsl" + "voxelRadianceSecondaryBounceCS.hlsl" + "voxelSceneCopyClearCS.hlsl" + "voxelClearOnlyNormalCS.hlsl" + "upsample_bilateral_float1CS.hlsl" + "upsample_bilateral_float4CS.hlsl" + "upsample_bilateral_unorm1CS.hlsl" + "upsample_bilateral_unorm4CS.hlsl" + "temporalaaCS.hlsl" + "tileFrustumsCS.hlsl" + "tonemapCS.hlsl" + "ssr_resolveCS.hlsl" + "ssr_temporalCS.hlsl" + "ssaoCS.hlsl" + "ssr_medianCS.hlsl" + "ssr_raytraceCS.hlsl" + "sharpenCS.hlsl" + "skinningCS.hlsl" + "skinningCS_LDS.hlsl" + "resolveMSAADepthStencilCS.hlsl" + "raytrace_shadeCS.hlsl" + "raytrace_tilesortCS.hlsl" + "raytrace_kickjobsCS.hlsl" + "raytrace_launchCS.hlsl" + "paint_textureCS.hlsl" + "raytrace_closesthitCS.hlsl" + "oceanUpdateDisplacementMapCS.hlsl" + "oceanUpdateGradientFoldingCS.hlsl" + "oceanSimulatorCS.hlsl" + "msao_interleaveCS.hlsl" + "msao_preparedepthbuffers1CS.hlsl" + "msao_preparedepthbuffers2CS.hlsl" + "msao_blurupsampleCS.hlsl" + "msao_blurupsampleCS_blendout.hlsl" + "msao_blurupsampleCS_premin.hlsl" + "msao_blurupsampleCS_premin_blendout.hlsl" + "msaoCS.hlsl" + "motionblur_neighborhoodMaxVelocityCS.hlsl" + "motionblur_tileMaxVelocity_horizontalCS.hlsl" + "motionblur_tileMaxVelocity_verticalCS.hlsl" + "luminancePass2CS.hlsl" + "motionblur_kickjobsCS.hlsl" + "motionblurCS.hlsl" + "motionblurCS_cheap.hlsl" + "motionblurCS_earlyexit.hlsl" + "lineardepthCS.hlsl" + "luminancePass1CS.hlsl" + "lightCullingCS_DEFERRED_ADVANCED_DEBUG.hlsl" + "lightCullingCS_DEFERRED_DEBUG.hlsl" + "lightShaftsCS.hlsl" + "lightCullingCS_ADVANCED_DEBUG.hlsl" + "lightCullingCS_DEBUG.hlsl" + "lightCullingCS_DEFERRED.hlsl" + "lightCullingCS_DEFERRED_ADVANCED.hlsl" + "lightCullingCS.hlsl" + "lightCullingCS_ADVANCED.hlsl" + "hbaoCS.hlsl" + "gpusortlib_sortInnerCS.hlsl" + "gpusortlib_sortStepCS.hlsl" + "gpusortlib_kickoffSortCS.hlsl" + "gpusortlib_sortCS.hlsl" + "fxaaCS.hlsl" + "filterEnvMapCS.hlsl" + "fft_512x512_c2c_CS.hlsl" + "fft_512x512_c2c_v2_CS.hlsl" + "emittedparticle_sphpartitionCS.hlsl" + "emittedparticle_sphpartitionoffsetsCS.hlsl" + "emittedparticle_sphpartitionoffsetsresetCS.hlsl" + "emittedparticle_simulateCS_SORTING.hlsl" + "emittedparticle_simulateCS_SORTING_DEPTHCOLLISIONS.hlsl" + "emittedparticle_sphdensityCS.hlsl" + "emittedparticle_sphforceCS.hlsl" + "emittedparticle_kickoffUpdateCS.hlsl" + "emittedparticle_simulateCS_DEPTHCOLLISIONS.hlsl" + "emittedparticle_emitCS.hlsl" + "emittedparticle_emitCS_FROMMESH.hlsl" + "emittedparticle_emitCS_volume.hlsl" + "emittedparticle_finishUpdateCS.hlsl" + "downsample4xCS.hlsl" + "depthoffield_prepassCS_earlyexit.hlsl" + "depthoffield_mainCS_cheap.hlsl" + "depthoffield_mainCS_earlyexit.hlsl" + "depthoffield_postfilterCS.hlsl" + "depthoffield_kickjobsCS.hlsl" + "copytexture2D_float4_borderexpandCS.hlsl" + "copytexture2D_unorm4_borderexpandCS.hlsl" + "copytexture2D_unorm4CS.hlsl" + "copytexture2D_float4CS.hlsl" + "chromatic_aberrationCS.hlsl" + "bvh_hierarchyCS.hlsl" + "bvh_primitivesCS.hlsl" + "bvh_propagateaabbCS.hlsl" + "blur_gaussian_wide_unorm1CS.hlsl" + "blur_gaussian_wide_unorm4CS.hlsl" + "blur_gaussian_unorm1CS.hlsl" + "blur_gaussian_unorm4CS.hlsl" + "blur_gaussian_wide_float1CS.hlsl" + "blur_gaussian_wide_float3CS.hlsl" + "blur_gaussian_wide_float4CS.hlsl" + "blur_bilateral_wide_unorm4CS.hlsl" + "blur_gaussian_float1CS.hlsl" + "blur_gaussian_float3CS.hlsl" + "blur_bilateral_unorm4CS.hlsl" + "blur_bilateral_wide_float1CS.hlsl" + "blur_bilateral_wide_float3CS.hlsl" + "blur_bilateral_wide_float4CS.hlsl" + "blur_bilateral_wide_unorm1CS.hlsl" + "blur_bilateral_float1CS.hlsl" + "blur_bilateral_float3CS.hlsl" + "blur_bilateral_float4CS.hlsl" + "blur_bilateral_unorm1CS.hlsl" + "bloomcombineCS.hlsl" + "voxelSceneCopyClearCS_TemporalSmoothing.hlsl" + "normalsfromdepthCS.hlsl" + "volumetricCloud_shapenoiseCS.hlsl" + "volumetricCloud_detailnoiseCS.hlsl" + "volumetricCloud_curlnoiseCS.hlsl" + "volumetricCloud_weathermapCS.hlsl" + "volumetricCloud_renderCS.hlsl" + "volumetricCloud_reprojectCS.hlsl" + "volumetricCloud_finalCS.hlsl" + "shadingRateClassificationCS.hlsl" + "shadingRateClassificationCS_DEBUG.hlsl" +) + +set(SHADERS_DS + "objectDS.hlsl" +) + +set(SHADERS_PS + "emittedparticlePS_soft.hlsl" + "screenPS.hlsl" + "imagePS_separatenormalmap.hlsl" + "imagePS_separatenormalmap_bicubic.hlsl" + "imagePS_backgroundblur_masked.hlsl" + "imagePS_masked.hlsl" + "imagePS_backgroundblur.hlsl" + "imagePS.hlsl" + "emittedparticlePS_soft_lighting.hlsl" + "oceanSurfacePS.hlsl" + "deferredPS.hlsl" + "hairparticlePS_forward.hlsl" + "hairparticlePS_tiledforward.hlsl" + "impostorPS_forward.hlsl" + "impostorPS_tiledforward.hlsl" + "volumetricLight_SpotPS.hlsl" + "volumetricLight_PointPS.hlsl" + "volumetricLight_DirectionalPS.hlsl" + "voxelPS.hlsl" + "vertexcolorPS.hlsl" + "trailPS.hlsl" + "tubeLightPS.hlsl" + "upsample_bilateralPS.hlsl" + "sunPS.hlsl" + "sssPS.hlsl" + "sphereLightPS.hlsl" + "spotLightPS.hlsl" + "skyPS_dynamic.hlsl" + "skyPS_static.hlsl" + "shadowPS_transparent.hlsl" + "shadowPS_water.hlsl" + "shadowPS_alphatest.hlsl" + "rectangleLightPS.hlsl" + "renderlightmapPS.hlsl" + "raytrace_debugbvhPS.hlsl" + "pointLightPS.hlsl" + "outlinePS.hlsl" + "oceanSurfaceSimplePS.hlsl" + "objectPS_tiledforward_transparent_pom.hlsl" + "objectPS_tiledforward_water.hlsl" + "objectPS_voxelizer.hlsl" + "objectPS_voxelizer_terrain.hlsl" + "objectPS_tiledforward_transparent.hlsl" + "objectPS_tiledforward_transparent_normalmap.hlsl" + "objectPS_tiledforward_transparent_normalmap_planarreflection.hlsl" + "objectPS_tiledforward_transparent_normalmap_pom.hlsl" + "objectPS_tiledforward_transparent_planarreflection.hlsl" + "objectPS_tiledforward_normalmap_pom.hlsl" + "objectPS_tiledforward_planarreflection.hlsl" + "objectPS_tiledforward_pom.hlsl" + "objectPS_tiledforward_terrain.hlsl" + "objectPS_textureonly.hlsl" + "objectPS_tiledforward.hlsl" + "objectPS_tiledforward_normalmap.hlsl" + "objectPS_tiledforward_normalmap_planarreflection.hlsl" + "objectPS_forward_transparent_pom.hlsl" + "objectPS_forward_water.hlsl" + "objectPS_hologram.hlsl" + "objectPS_paintradius.hlsl" + "objectPS_simplest.hlsl" + "objectPS_forward_transparent_normalmap_planarreflection.hlsl" + "objectPS_forward_transparent_normalmap_pom.hlsl" + "objectPS_forward_transparent_planarreflection.hlsl" + "objectPS_forward_pom.hlsl" + "objectPS_forward_terrain.hlsl" + "objectPS_forward_transparent.hlsl" + "objectPS_forward_transparent_normalmap.hlsl" + "objectPS_forward.hlsl" + "objectPS_forward_normalmap.hlsl" + "objectPS_forward_normalmap_planarreflection.hlsl" + "objectPS_forward_normalmap_pom.hlsl" + "objectPS_forward_planarreflection.hlsl" + "objectPS_deferred_normalmap_pom.hlsl" + "objectPS_deferred_planarreflection.hlsl" + "objectPS_deferred_pom.hlsl" + "objectPS_deferred_terrain.hlsl" + "objectPS_blackout.hlsl" + "objectPS_debug.hlsl" + "objectPS_deferred.hlsl" + "objectPS_deferred_normalmap.hlsl" + "objectPS_deferred_normalmap_planarreflection.hlsl" + "objectPS_alphatestonly.hlsl" + "lightVisualizerPS.hlsl" + "lensFlarePS.hlsl" + "impostorPS_wire.hlsl" + "impostorPS_deferred.hlsl" + "impostorPS_simple.hlsl" + "impostorPS_alphatestonly.hlsl" + "hairparticlePS_tiledforward_transparent.hlsl" + "hairparticlePS_deferred.hlsl" + "hairparticlePS_forward_transparent.hlsl" + "hairparticlePS_simplest.hlsl" + "hairparticlePS_alphatestonly.hlsl" + "forceFieldVisualizerPS.hlsl" + "fontPS.hlsl" + "environmentalLightPS.hlsl" + "envMap_skyPS_static.hlsl" + "envMap_skyPS_dynamic.hlsl" + "envMapPS.hlsl" + "envMapPS_terrain.hlsl" + "emittedparticlePS_soft_distortion.hlsl" + "downsampleDepthBuffer4xPS.hlsl" + "emittedparticlePS_simplest.hlsl" + "dirLightPS.hlsl" + "discLightPS.hlsl" + "decalPS.hlsl" + "cubeMapPS.hlsl" + "circlePS.hlsl" + "captureImpostorPS_normal.hlsl" + "captureImpostorPS_surface.hlsl" + "captureImpostorPS_albedo.hlsl" +) + +set(SHADERS_VS + "hairparticleVS.hlsl" + "emittedparticleVS.hlsl" + "imageVS.hlsl" + "fontVS.hlsl" + "voxelVS.hlsl" + "vertexcolorVS.hlsl" + "vSpotLightVS.hlsl" + "vTubeLightVS.hlsl" + "vDiscLightVS.hlsl" + "vPointLightVS.hlsl" + "vRectangleLightVS.hlsl" + "vSphereLightVS.hlsl" + "trailVS.hlsl" + "sphereVS.hlsl" + "spotLightVS.hlsl" + "skyVS.hlsl" + "shadowVS_transparent.hlsl" + "shadowVS.hlsl" + "shadowVS_alphatest.hlsl" + "screenVS.hlsl" + "renderlightmapVS.hlsl" + "raytrace_screenVS.hlsl" + "pointLightVS.hlsl" + "oceanSurfaceVS.hlsl" + "objectVS_simple.hlsl" + "objectVS_simple_tessellation.hlsl" + "objectVS_voxelizer.hlsl" + "objectVS_common.hlsl" + "objectVS_common_tessellation.hlsl" + "objectVS_debug.hlsl" + "objectVS_positionstream.hlsl" + "lensFlareVS.hlsl" + "impostorVS.hlsl" + "forceFieldPointVisualizerVS.hlsl" + "forceFieldPlaneVisualizerVS.hlsl" + "envMap_skyVS.hlsl" + "envMapVS.hlsl" + "dirLightVS.hlsl" + "decalVS.hlsl" + "cubeShadowVS.hlsl" + "cubeShadowVS_alphatest.hlsl" + "cubeVS.hlsl" +) + +set(SHADERS_GS + "voxelGS.hlsl" + "objectGS_voxelizer.hlsl" + "lensFlareGS.hlsl" +) + +set(SHADERS_HS + "objectHS.hlsl" +) + +set(SHADERS_LIB +) + + +SET(SPIRV_BINARY_FILES) + + +#dxc (DirectX Shader Compiler) is installed as part of the Vulkan SDK +function(Generate_Shaders SHADERS_SRC_LIST SHADER_TYPE) + foreach (Shader IN LISTS ${SHADERS_SRC_LIST}) + get_filename_component(FILE_NAME ${Shader} NAME_WLE) + message(STATUS "SHADER ${SHADER_TYPE} ${FILE_NAME}") + if (${SHADER_TYPE} STREQUAL vs OR ${SHADER_TYPE} STREQUAL ds OR ${SHADER_TYPE} STREQUAL gs) + set(INVERT_Y TRUE) + endif() + set(SPIRV_OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/${OUTPUT_DIR}/${FILE_NAME}.cso") + set(SPIRV_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/../${Shader}") + add_custom_command( + OUTPUT ${SPIRV_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CURRENT_SOURCE_DIR}/spirv/" + COMMAND "dxc" + "${SPIRV_SOURCE}" + -T "${SHADER_TYPE}_6_5" + -I "${CMAKE_CURRENT_SOURCE_DIR}/../" + -D SPIRV + $<$:-fvk-invert-y> + -spirv +# -O1 + -fvk-use-dx-layout + -flegacy-macro-expansion + -Fo ${SPIRV_OUTPUT} + -fspv-target-env=vulkan1.2 + -fvk-t-shift 1000 all + -fvk-u-shift 2000 all + -fvk-s-shift 3000 all +# -fspv-extension=KHR + DEPENDS ${SPIRV_SOURCE} + ) + list(APPEND SPIRV_BINARY_FILES ${SPIRV_OUTPUT}) + endforeach() + set(SPIRV_BINARY_FILES ${SPIRV_BINARY_FILES} PARENT_SCOPE) +endfunction() + + +Generate_Shaders(SHADERS_VS vs) +Generate_Shaders(SHADERS_HS hs) +Generate_Shaders(SHADERS_DS ds) +Generate_Shaders(SHADERS_GS gs) +Generate_Shaders(SHADERS_PS ps) +Generate_Shaders(SHADERS_CS cs) +Generate_Shaders(SHADERS_LIB lib) + +add_custom_target( + Shaders_SPIRV + DEPENDS ${SPIRV_BINARY_FILES} +) diff --git a/WickedEngine/volumetricCloud_renderCS.hlsl b/WickedEngine/volumetricCloud_renderCS.hlsl index 0d10d8867..5e43217a5 100644 --- a/WickedEngine/volumetricCloud_renderCS.hlsl +++ b/WickedEngine/volumetricCloud_renderCS.hlsl @@ -1,7 +1,7 @@ // References: // GPU Pro 7: Real-Time Volumetric Cloudscapes - A. Schneider // Follow up presentation: http://advances.realtimerendering.com/s2017/Nubis%20-%20Authoring%20Realtime%20Volumetric%20Cloudscapes%20with%20the%20Decima%20Engine%20-%20Final%20.pdf -// R. Hgfeldt, "Convincing Cloud Rendering An Implementation of Real-Time Dynamic Volumetric Clouds in Frostbite" +// R. Hogfeldt, "Convincing Cloud Rendering An Implementation of Real-Time Dynamic Volumetric Clouds in Frostbite" // F. Bauer, "Creating the Atmospheric World of Red Dead Redemption 2: A Complete and Integrated Solution" in Advances in Real-Time Rendering in Games, Siggraph 2019. #include "globals.hlsli" @@ -19,7 +19,7 @@ * * Right now the clouds use the lighting system, as described in GPU Pro 7 which isn't entirely physical. (+ phase functions) * However, it is possible to make it more physically accurate using the volumetric integration and participating media method from Frostbite. - * See paper from Sbastian Hillary describing their approach: https://media.contentapi.ea.com/content/dam/eacom/frostbite/files/s2016-pbs-frostbite-sky-clouds-new.pdf + * See paper from Sebastian Hillary describing their approach: https://media.contentapi.ea.com/content/dam/eacom/frostbite/files/s2016-pbs-frostbite-sky-clouds-new.pdf * Small example: https://www.shadertoy.com/view/XlBSRz * */ diff --git a/WickedEngine/wiAudio.cpp b/WickedEngine/wiAudio.cpp index 5e6f8e80f..02deec8b0 100644 --- a/WickedEngine/wiAudio.cpp +++ b/WickedEngine/wiAudio.cpp @@ -503,9 +503,9 @@ namespace wiAudio { void Initialize() {} - bool CreateSound(const std::string& filename, Sound* sound) {} - bool CreateSound(const std::vector& data, Sound* sound) {} - bool CreateSoundInstance(const Sound* sound, SoundInstance* instance) {} + bool CreateSound(const std::string& filename, Sound* sound) { return false; } + bool CreateSound(const std::vector& data, Sound* sound) { return false; } + bool CreateSoundInstance(const Sound* sound, SoundInstance* instance) { return false; } void Play(SoundInstance* instance) {} void Pause(SoundInstance* instance) {} diff --git a/WickedEngine/wiBackLog.cpp b/WickedEngine/wiBackLog.cpp index b04810900..3bb16befb 100644 --- a/WickedEngine/wiBackLog.cpp +++ b/WickedEngine/wiBackLog.cpp @@ -164,6 +164,8 @@ namespace wiBackLog #ifdef _WIN32 OutputDebugStringA(ss.str().c_str()); +#else + std::cout << ss.str(); #endif // _WIN32 } void input(const char& input) diff --git a/WickedEngine/wiGraphicsDevice_DX11.h b/WickedEngine/wiGraphicsDevice_DX11.h index ae4766837..05756de45 100644 --- a/WickedEngine/wiGraphicsDevice_DX11.h +++ b/WickedEngine/wiGraphicsDevice_DX11.h @@ -2,7 +2,7 @@ #if __has_include("d3d11_3.h") #define WICKEDENGINE_BUILD_DX11 -#endif // HAS VULKAN +#endif // HAS DX11 #ifdef WICKEDENGINE_BUILD_DX11 #include "CommonInclude.h" diff --git a/WickedEngine/wiGraphicsDevice_DX12.h b/WickedEngine/wiGraphicsDevice_DX12.h index 4cd66e5ae..7348c26c0 100644 --- a/WickedEngine/wiGraphicsDevice_DX12.h +++ b/WickedEngine/wiGraphicsDevice_DX12.h @@ -2,7 +2,7 @@ #if __has_include("d3d12.h") #define WICKEDENGINE_BUILD_DX12 -#endif // HAS VULKAN +#endif // HAS DX12 #ifdef WICKEDENGINE_BUILD_DX12 #include "CommonInclude.h" diff --git a/WickedEngine/wiGraphicsDevice_Vulkan.cpp b/WickedEngine/wiGraphicsDevice_Vulkan.cpp index 0066d6267..972730a60 100644 --- a/WickedEngine/wiGraphicsDevice_Vulkan.cpp +++ b/WickedEngine/wiGraphicsDevice_Vulkan.cpp @@ -20,6 +20,11 @@ #include #include +#ifdef SDL2 +#include +#include "sdl2.h" +#endif + // Enabling ray tracing might crash RenderDoc: #define ENABLE_RAYTRACING_EXTENSION @@ -604,10 +609,12 @@ namespace Vulkan_Internal }; bool checkValidationLayerSupport() { uint32_t layerCount; - vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + VkResult res = vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + assert(res == VK_SUCCESS); std::vector availableLayers(layerCount); - vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + res = vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + assert(res == VK_SUCCESS); for (const char* layerName : validationLayers) { bool layerFound = false; @@ -639,8 +646,10 @@ namespace Vulkan_Internal std::stringstream ss(""); ss << "[VULKAN validation layer]: " << msg << std::endl; - std::cerr << ss.str(); + std::clog << ss.str(); +#ifdef _WIN32 OutputDebugStringA(ss.str().c_str()); +#endif return VK_FALSE; } @@ -673,7 +682,9 @@ namespace Vulkan_Internal int i = 0; for (const auto& queueFamily : queueFamilies) { VkBool32 presentSupport = false; - vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + VkResult res = vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + assert(res == VK_SUCCESS); + if (indices.presentFamily < 0 && queueFamily.queueCount > 0 && presentSupport) { indices.presentFamily = i; } @@ -701,22 +712,27 @@ namespace Vulkan_Internal SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device, VkSurfaceKHR surface) { SwapChainSupportDetails details; - vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + assert(res == VK_SUCCESS); uint32_t formatCount; - vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + res = vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + assert(res == VK_SUCCESS); if (formatCount != 0) { details.formats.resize(formatCount); - vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + res = vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + assert(res == VK_SUCCESS); } uint32_t presentModeCount; - vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + res = vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + assert(res == VK_SUCCESS); if (presentModeCount != 0) { details.presentModes.resize(presentModeCount); - vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + res = vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + assert(res == VK_SUCCESS); } return details; @@ -750,9 +766,11 @@ namespace Vulkan_Internal } uint32_t extensionCount; - vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + VkResult res = vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + assert(res == VK_SUCCESS); std::vector available(extensionCount); - vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, available.data()); + res = vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, available.data()); + assert(res == VK_SUCCESS); for (auto& x : required_deviceExtensions) { @@ -1252,7 +1270,8 @@ using namespace Vulkan_Internal; if (descriptorPool != VK_NULL_HANDLE) { - vkResetDescriptorPool(device->device, descriptorPool, 0); + VkResult res = vkResetDescriptorPool(device->device, descriptorPool, 0); + assert(res == VK_SUCCESS); } memset(CBV, 0, sizeof(CBV)); @@ -2165,6 +2184,11 @@ using namespace Vulkan_Internal; GetClientRect(window, &rect); RESOLUTIONWIDTH = rect.right - rect.left; RESOLUTIONHEIGHT = rect.bottom - rect.top; +#elif SDL2 + int width, height; + SDL_GetWindowSize(window, &width, &height); + RESOLUTIONWIDTH = width; + RESOLUTIONHEIGHT = height; #endif // _WIN32 VkResult res; @@ -2181,15 +2205,27 @@ using namespace Vulkan_Internal; // Enumerate available extensions: uint32_t extensionCount = 0; - vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); + res = vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); + assert(res == VK_SUCCESS); std::vector extensions(extensionCount); - vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data()); + res = vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data()); + assert(res == VK_SUCCESS); std::vector extensionNames; extensionNames.push_back(VK_KHR_SURFACE_EXTENSION_NAME); extensionNames.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); #ifdef _WIN32 extensionNames.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); +#elif SDL2 + { + uint32_t extensionCount; + SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, nullptr); + std::vector extensionNames_sdl(extensionCount); + SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, extensionNames_sdl.data()); + extensionNames.reserve(extensionNames.size() + extensionNames_sdl.size()); + extensionNames.insert(extensionNames.begin(), + extensionNames_sdl.cbegin(), extensionNames_sdl.cend()); + } #endif // _WIN32 bool enableValidationLayers = debuglayer; @@ -2245,6 +2281,11 @@ using namespace Vulkan_Internal; if (!CreateWin32SurfaceKHR || CreateWin32SurfaceKHR(instance, &createInfo, nullptr, &surface) != VK_SUCCESS) { assert(0); } +#elif SDL2 + if (!SDL_Vulkan_CreateSurface(window, instance, &surface)) + { + throw sdl2::SDLError("Error creating a vulkan surface"); + } #else #error WICKEDENGINE VULKAN DEVICE ERROR: PLATFORM NOT SUPPORTED #endif // _WIN32 @@ -2254,7 +2295,8 @@ using namespace Vulkan_Internal; // Enumerating and creating devices: { uint32_t deviceCount = 0; - vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + VkResult res = vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + assert(res == VK_SUCCESS); if (deviceCount == 0) { wiHelper::messageBox("failed to find GPUs with Vulkan support!"); @@ -2262,7 +2304,8 @@ using namespace Vulkan_Internal; } std::vector devices(deviceCount); - vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + res = vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + assert(res == VK_SUCCESS); device_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; device_properties_1_1.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES; @@ -2316,9 +2359,11 @@ using namespace Vulkan_Internal; std::vector enabled_deviceExtensions = required_deviceExtensions; - vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, nullptr); + res = vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, nullptr); + assert(res == VK_SUCCESS); std::vector available_deviceExtensions(extensionCount); - vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, available_deviceExtensions.data()); + res = vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, available_deviceExtensions.data()); + assert(res == VK_SUCCESS); if (checkDeviceExtensionSupport(VK_KHR_SPIRV_1_4_EXTENSION_NAME, available_deviceExtensions)) { @@ -2469,7 +2514,8 @@ using namespace Vulkan_Internal; VkFenceCreateInfo fenceInfo = {}; fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; //fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - vkCreateFence(device, &fenceInfo, nullptr, &frames[fr].frameFence); + VkResult res = vkCreateFence(device, &fenceInfo, nullptr, &frames[fr].frameFence); + assert(res == VK_SUCCESS); } // Create resources for transition command buffer: @@ -2735,8 +2781,10 @@ using namespace Vulkan_Internal; } GraphicsDevice_Vulkan::~GraphicsDevice_Vulkan() { - vkQueueWaitIdle(graphicsQueue); - vkQueueWaitIdle(presentQueue); + VkResult res = vkQueueWaitIdle(graphicsQueue); + assert(res == VK_SUCCESS); + res = vkQueueWaitIdle(presentQueue); + assert(res == VK_SUCCESS); for (auto& frame : frames) { @@ -2878,10 +2926,12 @@ using namespace Vulkan_Internal; vkDestroySwapchainKHR(device, createInfo.oldSwapchain, nullptr); } - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + res = vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + assert(res == VK_SUCCESS); assert(BACKBUFFER_COUNT <= imageCount); swapChainImages.resize(imageCount); - vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + res = vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + assert(res == VK_SUCCESS); swapChainImageFormat = surfaceFormat.format; VkDebugUtilsObjectNameInfoEXT info = {}; @@ -5561,7 +5611,8 @@ using namespace Vulkan_Internal; { VkClearValue clearColor = { 0.0f, 0.0f, 0.0f, 1.0f }; - vkAcquireNextImageKHR(device, swapChain, 0xFFFFFFFFFFFFFFFF, imageAvailableSemaphore, VK_NULL_HANDLE, &swapChainImageIndex); + VkResult res = vkAcquireNextImageKHR(device, swapChain, 0xFFFFFFFFFFFFFFFF, imageAvailableSemaphore, VK_NULL_HANDLE, &swapChainImageIndex); + assert(res == VK_SUCCESS); VkRenderPassBeginInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; @@ -5593,7 +5644,8 @@ using namespace Vulkan_Internal; presentInfo.pImageIndices = &swapChainImageIndex; presentInfo.pResults = nullptr; // Optional - vkQueuePresentKHR(presentQueue, &presentInfo); + VkResult res = vkQueuePresentKHR(presentQueue, &presentInfo); + assert(res == VK_SUCCESS); } CommandList GraphicsDevice_Vulkan::BeginCommandList() @@ -5853,7 +5905,8 @@ using namespace Vulkan_Internal; void GraphicsDevice_Vulkan::WaitForGPU() { - vkQueueWaitIdle(graphicsQueue); + VkResult res = vkQueueWaitIdle(graphicsQueue); + assert(res == VK_SUCCESS); } void GraphicsDevice_Vulkan::ClearPipelineStateCache() { diff --git a/WickedEngine/wiHelper.cpp b/WickedEngine/wiHelper.cpp index c09fad666..d4c913a71 100644 --- a/WickedEngine/wiHelper.cpp +++ b/WickedEngine/wiHelper.cpp @@ -22,12 +22,16 @@ #include // openfile #include #endif // PLATFORM_UWP +#else +#include +#include "Utility/portable-file-dialogs.h" #endif // _WIN32 using namespace std; namespace wiHelper { + string toUpper(const std::string& s) { std::string result; @@ -62,6 +66,8 @@ namespace wiHelper } #ifdef _WIN32 CreateDirectoryA(directory.c_str(), 0); +#elif SDL2 + std::filesystem::create_directory(directory.c_str()); #endif // _WIN32 std::string filename = name; @@ -242,19 +248,19 @@ namespace wiHelper string GetApplicationDirectory() { - static string appDir; - static bool initComplete = false; - if (!initComplete) - { #ifdef _WIN32 + static std::string appDir; + if (appDir.empty()) + { CHAR fileName[1024] = {}; GetModuleFileNameA(NULL, fileName, arraysize(fileName)); appDir = GetDirectoryFromPath(fileName); -#else - // TODO -#endif // _WIN32 - initComplete = true; } +#elif SDL2 + static std::string appDir = std::string(SDL_GetBasePath()); +#else + static std::string appDir; +#endif // _WIN32 return appDir; } @@ -348,7 +354,7 @@ namespace wiHelper // Replace all slashes with backslashes: #ifdef PLATFORM_UWP std::replace(expanded.begin(), expanded.end(), '/', '\\'); -#endif // _WIN32 +#endif // PLATFORM_UWP size_t pos; while ((pos = expanded.find("..")) != string::npos) @@ -742,9 +748,46 @@ namespace wiHelper #endif // PLATFORM_UWP #else + if (!pfd::settings::available()) { + const char *message = "No dialog backend available"; +#ifdef SDL2 + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, + "File dialog error!", + message, + nullptr); +#endif + std::cerr << message << std::endl; + } - // TODO + std::vector extensions = {params.description}; + extensions.reserve(params.extensions.size()+1); + extensions.insert(extensions.end(), params.extensions.cbegin(), params.extensions.cend()); + switch (params.type) { + case FileDialogParams::OPEN: { + std::vector selection = pfd::open_file( + params.description, + wiHelper::GetOriginalWorkingDirectory(), + extensions + // allow multi selection here + ).result(); + // result() will wait for user action before returning. + // This operation will block and return the user choice + + if (!selection.empty()) { + onSuccess(selection[0]); + } + //TODO what happens if the user cancelled the action? Is there not a onFailure callback? + } + case FileDialogParams::SAVE: { + std::string destination = pfd::save_file(params.description, + wiHelper::GetOriginalWorkingDirectory(), + extensions + // remove overwrite warning here + ).result(); + onSuccess(destination); + } + } #endif // _WIN32 } @@ -786,6 +829,9 @@ namespace wiHelper } #else int num = 0; // TODO + const char * message = "int StringConvert(const char* from, wchar_t* to) not implemented"; + std::cerr << message << std::endl; + throw std::runtime_error(message); #endif // _WIN32 return num; } @@ -800,6 +846,9 @@ namespace wiHelper } #else int num = 0; // TODO + const char * message = "int StringConvert(const wchar_t* from, char* to) not implemented"; + std::cerr << message << std::endl; + throw std::runtime_error(message); #endif // _WIN32 return num; } diff --git a/WickedEngine/wiInput.cpp b/WickedEngine/wiInput.cpp index 2f61f1606..ad3ed344a 100644 --- a/WickedEngine/wiInput.cpp +++ b/WickedEngine/wiInput.cpp @@ -2,6 +2,7 @@ #include "wiPlatform.h" #include "wiXInput.h" #include "wiRawInput.h" +#include "wiSDLInput.h" #include "wiHelper.h" #include "wiBackLog.h" #include "wiProfiler.h" @@ -12,6 +13,10 @@ #include #include +#ifdef SDL2 +#include +#endif + using namespace std; namespace wiInput @@ -153,6 +158,7 @@ namespace wiInput void Initialize() { wiRawInput::Initialize(); + wiSDLInput::Initialize(); wiBackLog::post("wiInput Initialized"); initialized.store(true); @@ -169,16 +175,25 @@ namespace wiInput wiXInput::Update(); wiRawInput::Update(); + wiSDLInput::Update(); mouse.delta_wheel = 0; mouse.delta_position = XMFLOAT2(0, 0); + +#ifdef _WIN32 wiRawInput::GetMouseState(&mouse); // currently only the relative data can be used from this wiRawInput::GetKeyboardState(&keyboard); // it contains pressed buttons as "keyboard/typewriter" like, so no continuous presses // apparently checking the mouse here instead of Down() avoids missing the button presses (review!) - mouse.left_button_press |= KEY_DOWN(VK_LBUTTON); - mouse.middle_button_press |= KEY_DOWN(VK_MBUTTON); - mouse.right_button_press |= KEY_DOWN(VK_RBUTTON); + mouse.left_button_press |= KEY_DOWN(VK_LBUTTON); + mouse.middle_button_press |= KEY_DOWN(VK_MBUTTON); + mouse.right_button_press |= KEY_DOWN(VK_RBUTTON); +#elif SDL2 + wiSDLInput::GetMouseState(&mouse); + wiSDLInput::GetKeyboardState(&keyboard); + //TODO controllers + //TODO touch +#endif #ifdef PLATFORM_UWP static bool isRegisteredTouch = false; @@ -448,10 +463,103 @@ namespace wiInput case wiInput::KEYBOARD_BUTTON_PAGEUP: keycode = VK_PRIOR; break; +#elif SDL2 + case wiInput::KEYBOARD_BUTTON_UP: + keycode = SDL_SCANCODE_UP; + break; + case wiInput::KEYBOARD_BUTTON_DOWN: + keycode = SDL_SCANCODE_DOWN; + break; + case wiInput::KEYBOARD_BUTTON_LEFT: + keycode = SDL_SCANCODE_LEFT; + break; + case wiInput::KEYBOARD_BUTTON_RIGHT: + keycode = SDL_SCANCODE_RIGHT; + break; + case wiInput::KEYBOARD_BUTTON_SPACE: + keycode = SDL_SCANCODE_SPACE; + break; + case wiInput::KEYBOARD_BUTTON_RSHIFT: + keycode = SDL_SCANCODE_RSHIFT; + break; + case wiInput::KEYBOARD_BUTTON_LSHIFT: + keycode = SDL_SCANCODE_LSHIFT; + break; + case wiInput::KEYBOARD_BUTTON_F1: + keycode = SDL_SCANCODE_F1; + break; + case wiInput::KEYBOARD_BUTTON_F2: + keycode = SDL_SCANCODE_F2; + break; + case wiInput::KEYBOARD_BUTTON_F3: + keycode = SDL_SCANCODE_F3; + break; + case wiInput::KEYBOARD_BUTTON_F4: + keycode = SDL_SCANCODE_F4; + break; + case wiInput::KEYBOARD_BUTTON_F5: + keycode = SDL_SCANCODE_F5; + break; + case wiInput::KEYBOARD_BUTTON_F6: + keycode = SDL_SCANCODE_F6; + break; + case wiInput::KEYBOARD_BUTTON_F7: + keycode = SDL_SCANCODE_F7; + break; + case wiInput::KEYBOARD_BUTTON_F8: + keycode = SDL_SCANCODE_F8; + break; + case wiInput::KEYBOARD_BUTTON_F9: + keycode = SDL_SCANCODE_F9; + break; + case wiInput::KEYBOARD_BUTTON_F10: + keycode = SDL_SCANCODE_F10; + break; + case wiInput::KEYBOARD_BUTTON_F11: + keycode = SDL_SCANCODE_F11; + break; + case wiInput::KEYBOARD_BUTTON_F12: + keycode = SDL_SCANCODE_F12; + break; + case wiInput::KEYBOARD_BUTTON_ENTER: + keycode = SDL_SCANCODE_RETURN; + break; + case wiInput::KEYBOARD_BUTTON_ESCAPE: + keycode = SDL_SCANCODE_ESCAPE; + break; + case wiInput::KEYBOARD_BUTTON_HOME: + keycode = SDL_SCANCODE_HOME; + break; + case wiInput::KEYBOARD_BUTTON_LCONTROL: + keycode = SDL_SCANCODE_LCTRL; + break; + case wiInput::KEYBOARD_BUTTON_RCONTROL: + keycode = SDL_SCANCODE_RCTRL; + break; + case wiInput::KEYBOARD_BUTTON_DELETE: + keycode = SDL_SCANCODE_DELETE; + break; + case wiInput::KEYBOARD_BUTTON_BACKSPACE: + keycode = SDL_SCANCODE_BACKSPACE; + break; + case wiInput::KEYBOARD_BUTTON_PAGEDOWN: + keycode = SDL_SCANCODE_PAGEDOWN; + break; + case wiInput::KEYBOARD_BUTTON_PAGEUP: + keycode = SDL_SCANCODE_PAGEUP; + break; #endif // _WIN32 } +#ifdef _WIN32 return KEY_DOWN(keycode) || KEY_TOGGLE(keycode); +#elif SDL2 + int numkeys; + const uint8_t *state = SDL_GetKeyboardState(&numkeys); + return state[keycode] == 1; +#else +#error KEYBOARD INPUT NOT SUPPORTED +#endif } return false; @@ -548,6 +656,8 @@ namespace wiInput wiPlatform::GetWindow()->PointerCursor = cursor; } #endif +#elif SDL2 + SDL_ShowCursor(value ? SDL_ENABLE : SDL_DISABLE); #endif // _WIN32 } diff --git a/WickedEngine/wiPlatform.h b/WickedEngine/wiPlatform.h index 6ff2ad026..3eaa5f779 100644 --- a/WickedEngine/wiPlatform.h +++ b/WickedEngine/wiPlatform.h @@ -4,6 +4,7 @@ #include #include #include +#include #ifdef _WIN32 @@ -23,6 +24,11 @@ #endif // _WIN32 +#ifdef SDL2 +#include +#include "sdl2.h" +#endif + namespace wiPlatform { @@ -32,6 +38,8 @@ namespace wiPlatform #else using window_type = Platform::Agile; #endif // PLATFORM_UWP +#elif SDL2 + using window_type = SDL_Window*; #else using window_type = int; #endif // _WIN32 @@ -79,6 +87,20 @@ namespace wiPlatform #else GetWindowState().dpi = (int)Windows::Graphics::Display::DisplayInformation::GetForCurrentView()->LogicalDpi; #endif // PLATFORM_UWP +#elif SDL2 + int displayIndex = 0; + float ddpi; + float hdpi; + float vdpi; + int ret = SDL_GetDisplayDPI(displayIndex, &ddpi, &hdpi, &vdpi); + if (ret == 0) { + //TODO setting the correct DPI resolution messes up with the correct mouse position. + // I believe it's because SDL2 is reporting the correct pixel position while windows is reporting + // a corrected version of the mouse position that needs to be interpreted depending on the DPI. + //GetWindowState().dpi = ddpi; + } else { + std::clog << "Could not infer Display DPI from SDL: " << SDL_GetError() << std::endl; + } #endif // _WIN32 } inline int GetDPI() @@ -111,6 +133,10 @@ namespace wiPlatform #else Windows::UI::Popups::MessageDialog(ref new Platform::String(x.message.c_str()), ref new Platform::String(x.caption.c_str())).ShowAsync(); #endif // PLATFORM_UWP +#elif SDL2 + std::string title(x.caption.begin(), x.caption.end()); + std::string message(x.message.begin(), x.message.end()); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title.c_str(), message.c_str(), NULL); #endif // _WIN32 } state.messages.clear(); diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp index ca073c5ff..8d065dab3 100644 --- a/WickedEngine/wiRenderer.cpp +++ b/WickedEngine/wiRenderer.cpp @@ -1308,7 +1308,7 @@ void LoadShaders() wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(PS, pixelShaders[PSTYPE_CAPTUREIMPOSTOR_ALBEDO], "captureImpostorPS_albedo.cso"); }); wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(PS, pixelShaders[PSTYPE_CAPTUREIMPOSTOR_NORMAL], "captureImpostorPS_normal.cso"); }); wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(PS, pixelShaders[PSTYPE_CAPTUREIMPOSTOR_SURFACE], "captureImpostorPS_surface.cso"); }); - wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(PS, pixelShaders[PSTYPE_CUBEMAP], "cubemapPS.cso"); }); + wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(PS, pixelShaders[PSTYPE_CUBEMAP], "cubeMapPS.cso"); }); wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(PS, pixelShaders[PSTYPE_VERTEXCOLOR], "vertexcolorPS.cso"); }); wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(PS, pixelShaders[PSTYPE_SKY_STATIC], "skyPS_static.cso"); }); wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(PS, pixelShaders[PSTYPE_SKY_DYNAMIC], "skyPS_dynamic.cso"); }); @@ -1402,7 +1402,7 @@ void LoadShaders() wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(CS, computeShaders[CSTYPE_POSTPROCESS_SSR_RESOLVE], "ssr_resolveCS.cso"); }); wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(CS, computeShaders[CSTYPE_POSTPROCESS_SSR_TEMPORAL], "ssr_temporalCS.cso"); }); wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(CS, computeShaders[CSTYPE_POSTPROCESS_SSR_MEDIAN], "ssr_medianCS.cso"); }); - wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(CS, computeShaders[CSTYPE_POSTPROCESS_LIGHTSHAFTS], "lightshaftsCS.cso"); }); + wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(CS, computeShaders[CSTYPE_POSTPROCESS_LIGHTSHAFTS], "lightShaftsCS.cso"); }); wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(CS, computeShaders[CSTYPE_POSTPROCESS_DEPTHOFFIELD_TILEMAXCOC_HORIZONTAL], "depthoffield_tileMaxCOC_horizontalCS.cso"); }); wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(CS, computeShaders[CSTYPE_POSTPROCESS_DEPTHOFFIELD_TILEMAXCOC_VERTICAL], "depthoffield_tileMaxCOC_verticalCS.cso"); }); wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(CS, computeShaders[CSTYPE_POSTPROCESS_DEPTHOFFIELD_NEIGHBORHOODMAXCOC], "depthoffield_neighborhoodMaxCOCCS.cso"); }); @@ -6857,8 +6857,7 @@ void DrawDebugWorld(const CameraComponent& camera, CommandList cmd) device->BindConstantBuffer(PS, &material.constantBuffer, CB_GETBINDSLOT(MaterialCB), cmd); PaintRadiusCB cb; - cb.xPaintRadResolution.x = material.GetBaseColorMap()->GetDesc().Width; - cb.xPaintRadResolution.y = material.GetBaseColorMap()->GetDesc().Height; + cb.xPaintRadResolution = x.dimensions; cb.xPaintRadCenter = x.center; cb.xPaintRadRadius = x.radius; cb.xPaintRadUVSET = x.uvset; diff --git a/WickedEngine/wiRenderer.h b/WickedEngine/wiRenderer.h index 820d865de..601a95288 100644 --- a/WickedEngine/wiRenderer.h +++ b/WickedEngine/wiRenderer.h @@ -597,6 +597,7 @@ namespace wiRenderer uint32_t uvset = 0; float radius = 0; XMUINT2 center; + XMUINT2 dimensions; }; void DrawPaintRadius(const PaintRadius& paintrad); diff --git a/WickedEngine/wiSDLInput.cpp b/WickedEngine/wiSDLInput.cpp new file mode 100644 index 000000000..4e2b8c36d --- /dev/null +++ b/WickedEngine/wiSDLInput.cpp @@ -0,0 +1,188 @@ +#include "CommonInclude.h" +#include "wiInput.h" +#include "wiSDLInput.h" + +#ifdef SDL2 + +#include +#include + +namespace wiSDLInput +{ + wiInput::KeyboardState keyboard; + wiInput::MouseState mouse; + + //TODO controllers + //struct Internal_ControllerState + //{ + // HANDLE handle = NULL; + // bool is_xinput = false; + // std::wstring name; + // wiInput::ControllerState state; + //}; + //std::vector controllers; + + int to_wiInput(const SDL_Scancode &key); + + void Initialize() {} + void Update() + { + auto saved_x = mouse.position.x; + auto saved_y = mouse.position.y; + + // update keyboard and mouse to the latest value (in case of other input systems I suppose) +// keyboard = wiInput::KeyboardState(); +// mouse = wiInput::MouseState(); +// for (auto& internal_controller : controllers) +// { +// internal_controller.state = wiInput::ControllerState(); +// } + mouse.position.x = saved_x; + mouse.position.y = saved_y; + + std::vector events(1000); + + // This removes the only the inputs events from the event queue, leaving audio and window events for other + // section of the code + SDL_PumpEvents(); + int ret = SDL_PeepEvents(events.data(), events.size(), SDL_GETEVENT, SDL_KEYDOWN, SDL_MULTIGESTURE); + if (ret < 0) { + std::cerr << "Error Peeping event: " << SDL_GetError() << std::endl; + return; + } else if (ret > 0) { + events.resize(ret); + + for (const SDL_Event &event : events) { + switch (event.type) { + // Keyboard events + case SDL_KEYDOWN: // Key pressed + { + int converted = to_wiInput(event.key.keysym.scancode); + if (converted >= 0) { + keyboard.buttons[converted] = true; + } + break; + } + case SDL_KEYUP: // Key released + { + int converted = to_wiInput(event.key.keysym.scancode); + if (converted >= 0) { + keyboard.buttons[converted] = false; + } + break; + } + case SDL_TEXTEDITING: // Keyboard text editing (composition) + case SDL_TEXTINPUT: // Keyboard text input + case SDL_KEYMAPCHANGED: // Keymap changed due to a system event such as an + // input language or keyboard layout change. + break; + + + // mouse events + case SDL_MOUSEMOTION: // Mouse moved + mouse.position.x = event.motion.x; + mouse.position.y = event.motion.y; + mouse.delta_position.x += event.motion.xrel; + mouse.delta_position.y += event.motion.yrel; + // TODO you can update mouse buttons by reading event.motion.state + mouse.left_button_press = event.motion.state & SDL_BUTTON_LMASK; + mouse.right_button_press = event.motion.state & SDL_BUTTON_RMASK; + mouse.middle_button_press = event.motion.state & SDL_BUTTON_MMASK; + //mouse.x1_button_press = event.motion.state & SDL_BUTTON_X1MASK; + //mouse.x2_button_press = event.motion.state & SDL_BUTTON_X2MASK; + + break; + case SDL_MOUSEBUTTONDOWN: // Mouse button pressed + case SDL_MOUSEBUTTONUP: // Mouse button released + // handled at the bottom of this function + break; + case SDL_MOUSEWHEEL: // Mouse wheel motion + { + float delta = static_cast(event.wheel.y); + if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) { + delta *= -1; + } + mouse.delta_wheel += delta; + break; + } + + + // Joystick events + case SDL_JOYAXISMOTION: // Joystick axis motion + case SDL_JOYBALLMOTION: // Joystick trackball motion + case SDL_JOYHATMOTION: // Joystick hat position change + case SDL_JOYBUTTONDOWN: // Joystick button pressed + case SDL_JOYBUTTONUP: // Joystick button released + case SDL_JOYDEVICEADDED: // A new joystick has been inserted into the system + case SDL_JOYDEVICEREMOVED: // An opened joystick has been removed + break; + + + // Game controller events + case SDL_CONTROLLERAXISMOTION: // Game controller axis motion + case SDL_CONTROLLERBUTTONDOWN: // Game controller button pressed + case SDL_CONTROLLERBUTTONUP: // Game controller button released + case SDL_CONTROLLERDEVICEADDED: // A new Game controller has been inserted into the system + case SDL_CONTROLLERDEVICEREMOVED: // An opened Game controller has been removed + case SDL_CONTROLLERDEVICEREMAPPED: // The controller mapping was updated + break; + + + // Touch events + case SDL_FINGERDOWN: + case SDL_FINGERUP: + case SDL_FINGERMOTION: + break; + + + // Gesture events + case SDL_DOLLARGESTURE: + case SDL_DOLLARRECORD: + case SDL_MULTIGESTURE: + break; + } + } + } + + // asking directly some data instead of events + int x,y; + uint32_t mouse_buttons_state = SDL_GetMouseState(&x, &y); + mouse.position.x = x; + mouse.position.y = y; + mouse.left_button_press = mouse_buttons_state & SDL_BUTTON_LMASK; + mouse.right_button_press = mouse_buttons_state & SDL_BUTTON_RMASK; + mouse.middle_button_press = mouse_buttons_state & SDL_BUTTON_MMASK; + } + + void GetKeyboardState(wiInput::KeyboardState* state) { + *state = keyboard; + } + void GetMouseState(wiInput::MouseState* state) { + *state = mouse; + } + int GetMaxControllerCount() { return 0; } + bool GetControllerState(wiInput::ControllerState* state, int index) { return false; } + void SetControllerFeedback(const wiInput::ControllerFeedback& data, int index) {} + + + int to_wiInput(const SDL_Scancode &key) { + if (key < arraysize(keyboard.buttons)) + { + return key; + } + return -1; + } +} + +#else +namespace wiSDLInput +{ + void Initialize() {} + void Update() {} + void GetKeyboardState(wiInput::KeyboardState* state) {} + void GetMouseState(wiInput::MouseState* state) {} + int GetMaxControllerCount() { return 0; } + bool GetControllerState(wiInput::ControllerState* state, int index) { return false; } + void SetControllerFeedback(const wiInput::ControllerFeedback& data, int index) {} +} +#endif // _WIN32 \ No newline at end of file diff --git a/WickedEngine/wiSDLInput.h b/WickedEngine/wiSDLInput.h new file mode 100644 index 000000000..3ef37cf4d --- /dev/null +++ b/WickedEngine/wiSDLInput.h @@ -0,0 +1,32 @@ +#pragma once +#include "CommonInclude.h" +#include "wiInput.h" + +#ifdef SDL2 +#include +#endif + +namespace wiSDLInput +{ + // Call this once to register raw input devices + void Initialize(); + + // Updates the state of raw input devices, call once per frame + void Update(); + + // Writes the keyboard state into state parameter + void GetKeyboardState(wiInput::KeyboardState* state); + + // Writes the mouse state into state parameter + void GetMouseState(wiInput::MouseState* state); + + // Returns how many controller devices have received input ever. This doesn't correlate with which ones are currently available + int GetMaxControllerCount(); + + // Returns whether the controller identified by index parameter is available or not + // Id state parameter is not nullptr, and the controller is available, the state will be written into it + bool GetControllerState(wiInput::ControllerState* state, int index); + + // Sends feedback data for the controller identified by index parameter to output + void SetControllerFeedback(const wiInput::ControllerFeedback& data, int index); +} diff --git a/WickedEngine/wiStartupArguments.cpp b/WickedEngine/wiStartupArguments.cpp index a8fbcc599..67af2954c 100644 --- a/WickedEngine/wiStartupArguments.cpp +++ b/WickedEngine/wiStartupArguments.cpp @@ -28,6 +28,14 @@ namespace wiStartupArguments } + void Parse(int argc, char *argv[]) + { + for (int i=1; i +std::chrono::time_point CounterStart; +std::atomic_flag initialized = ATOMIC_FLAG_INIT; +#endif wiTimer::wiTimer() { +#ifdef _WIN32 if(CounterStart==0) - Start(); +#else + // does this CounterStart initialization need to be thread safe? + if (!initialized.test_and_set(std::memory_order_acquire)) +#endif + { + Start(); + } record(); } @@ -28,6 +41,8 @@ void wiTimer::Start() QueryPerformanceCounter(&li); CounterStart = li.QuadPart; +#else + CounterStart = std::chrono::high_resolution_clock::now(); #endif // _WIN32 } double wiTimer::TotalTime() @@ -37,7 +52,9 @@ double wiTimer::TotalTime() QueryPerformanceCounter(&li); return double(li.QuadPart-CounterStart)/PCFreq; #else - return 0; + auto now = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed_seconds = now - CounterStart; + return elapsed_seconds.count(); #endif // _WIN32 } diff --git a/WickedEngine/wiTimer.h b/WickedEngine/wiTimer.h index 466bb2039..b9aaa5727 100644 --- a/WickedEngine/wiTimer.h +++ b/WickedEngine/wiTimer.h @@ -9,12 +9,14 @@ public: wiTimer(); ~wiTimer(); + /// Resets the start time of the engine clock to `now` static void Start(); + /// Total time since the start of the engine clock in milliseconds static double TotalTime(); - //start recording + /// Start recording void record(); - //elapsed time since record() + /// Elapsed time since record() in milliseconds double elapsed(); }; diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index 319046d6c..e28fe0f01 100644 --- a/WickedEngine/wiVersion.cpp +++ b/WickedEngine/wiVersion.cpp @@ -7,9 +7,9 @@ namespace wiVersion // main engine core const int major = 0; // minor features, major updates, breaking API changes - const int minor = 47; + const int minor = 48; // minor bug fixes, alterations, refactors, updates - const int revision = 46; + const int revision = 0; const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision); diff --git a/other_licenses.txt b/other_licenses.txt index a1ea5307c..d62e3d08a 100644 --- a/other_licenses.txt +++ b/other_licenses.txt @@ -234,6 +234,24 @@ THE SOFTWARE. ############################################################################################################################### +samhocevar/portable-file-dialogs: + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + +############################################################################################################################### + ###############################################################################################################################