diff --git a/.gitignore b/.gitignore index e0c437752dc..d89a981f2c1 100644 --- a/.gitignore +++ b/.gitignore @@ -133,3 +133,4 @@ Desktop.ini .mailmap /apps*/ +openframeworksLib.vcxproj.user \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 09211ad8846..b96af8982f3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,4 @@ url = https://github.com/openframeworks/projectGenerator.git [submodule "scripts/apothecary"] path = scripts/apothecary - url = https://github.com/openframeworks/apothecary + url = https://github.com/openframeworks-vk/apothecary.git diff --git a/README.md b/README.md index 3ef7ed29e30..f5b0fc5fada 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,14 @@ +================ + +# Experimental Vulkan Renderer branch + +For mainline openFrameworks goto: +[https://github.com/openframeworks/openFrameworks](https://github.com/openframeworks/openFrameworks) + +Also take a look at the Vulkan specific README in [libs/openFrameworks/vk](https://github.com/openframeworks-vk/openFrameworks/tree/vk/libs/openFrameworks/vk) + +================ + [openFrameworks](http://openframeworks.cc/) ================ @@ -6,7 +17,6 @@ openFrameworks is a C++ toolkit for creative coding. If you are new to OF, welc [![Slack Status](https://ofslack.herokuapp.com/badge.svg)](https://ofslack.herokuapp.com) Build status --------- Linux, OSX, iOS and Android [![Build Status](https://travis-ci.org/openframeworks/openFrameworks.svg?branch=master)](https://travis-ci.org/openframeworks/openFrameworks) diff --git a/apps/devApps/testVk/addons.make b/apps/devApps/testVk/addons.make new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/devApps/testVk/bin/data/.gitkeep b/apps/devApps/testVk/bin/data/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/devApps/testVk/bin/data/default.frag b/apps/devApps/testVk/bin/data/default.frag new file mode 100644 index 00000000000..fcc1249adde --- /dev/null +++ b/apps/devApps/testVk/bin/data/default.frag @@ -0,0 +1,33 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +layout (location = 0) in vec4 inColor; +layout (location = 1) in vec3 inNormal; +layout (location = 2) in vec2 inTexCoord; + +// uniforms (resources) +layout (set = 0, binding = 0) uniform DefaultMatrices +{ + mat4 projectionMatrix; + mat4 modelMatrix; + mat4 viewMatrix; +}; +// layout (set = 1, binding = 0) uniform StyleSet +// { +// vec4 globalColor; +// } style; + + +layout (location = 0) out vec4 outFragColor; + +void main() +{ + + vec4 normalColor = vec4((inNormal + vec3(1.0)) * vec3(0.5) , 1.0); + vec4 vertexColor = inColor; + + // set the actual fragment color here + outFragColor = vertexColor; +} \ No newline at end of file diff --git a/apps/devApps/testVk/bin/data/default.vert b/apps/devApps/testVk/bin/data/default.vert new file mode 100644 index 00000000000..a7c3d21c3e4 --- /dev/null +++ b/apps/devApps/testVk/bin/data/default.vert @@ -0,0 +1,43 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// uniforms (resources) +layout (set = 0, binding = 0) uniform DefaultMatrices +{ + mat4 projectionMatrix; + mat4 modelMatrix; + mat4 viewMatrix; +}; // note: if you don't specify a variable name for the block its elements will live in the global namespace. + +layout (set = 0, binding = 1) uniform Style +{ + vec4 globalColor; +} style; + +// inputs (vertex attributes) +layout (location = 0) in vec3 inPos; +layout (location = 1) in vec4 inColor; +layout (location = 2) in vec3 inNormal; +layout (location = 3) in vec2 inTexCoord; + +// outputs +layout (location = 0) out vec4 outColor; +layout (location = 1) out vec3 outNormal; +layout (location = 2) out vec2 outTexCoord; + +// we override the built-in fixed function outputs +// to have more control over the SPIR-V code created. +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main() +{ + outNormal = (inverse(transpose( viewMatrix * modelMatrix)) * vec4(inNormal, 0.0)).xyz; + outColor = style.globalColor; + outTexCoord = inTexCoord; + gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(inPos.xyz, 1.0); +} diff --git a/apps/devApps/testVk/bin/data/fullScreenQuad.frag b/apps/devApps/testVk/bin/data/fullScreenQuad.frag new file mode 100644 index 00000000000..29f8a618098 --- /dev/null +++ b/apps/devApps/testVk/bin/data/fullScreenQuad.frag @@ -0,0 +1,14 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// inputs +layout (location = 0) in vec2 inTexCoord; + +// outputs +layout (location = 0) out vec4 outFragColor; + +void main(){ + outFragColor = vec4(inTexCoord, 1, 1.0); +} \ No newline at end of file diff --git a/apps/devApps/testVk/bin/data/fullScreenQuad.vert b/apps/devApps/testVk/bin/data/fullScreenQuad.vert new file mode 100644 index 00000000000..dbc2e6e10d4 --- /dev/null +++ b/apps/devApps/testVk/bin/data/fullScreenQuad.vert @@ -0,0 +1,24 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// outputs +layout (location = 0) out vec2 outTexCoord; + +// we override the built-in fixed function outputs +// to have more control over the SPIR-V code created. +out gl_PerVertex +{ + vec4 gl_Position; +}; + +// This shader built after a technique introduced in: +// http://www.saschawillems.de/?page_id=2122 + +void main() +{ + outTexCoord = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + gl_Position = vec4(outTexCoord * 2.0f + -1.0f, 0.0f, 1.0f); + +} \ No newline at end of file diff --git a/apps/devApps/testVk/icon.rc b/apps/devApps/testVk/icon.rc new file mode 100644 index 00000000000..7e26eb3534f --- /dev/null +++ b/apps/devApps/testVk/icon.rc @@ -0,0 +1,8 @@ +// Icon Resource Definition +#define MAIN_ICON 102 + +#if defined(_DEBUG) +MAIN_ICON ICON "icon_debug.ico" +#else +MAIN_ICON ICON "icon.ico" +#endif diff --git a/apps/devApps/testVk/src/main.cpp b/apps/devApps/testVk/src/main.cpp new file mode 100644 index 00000000000..2ff29588ac8 --- /dev/null +++ b/apps/devApps/testVk/src/main.cpp @@ -0,0 +1,38 @@ +#include "ofMain.h" +#include "ofApp.h" + +int main(){ + // Do basic initialisation (mostly setup timers, and randseed) + ofInit(); + + auto consoleLogger = new ofConsoleLoggerChannel(); + ofSetLoggerChannel( std::shared_ptr( consoleLogger, []( ofBaseLoggerChannel * lhs){} ) ); + + // Create a new window + auto mainWindow = std::make_shared(); + //auto mainWindow = std::make_shared(); + + // Store main window in mainloop + ofGetMainLoop()->addWindow( mainWindow ); + + { + ofVkWindowSettings settings; + settings.rendererSettings.setVkVersion( 1, 0, 46 ); + settings.rendererSettings.numSwapchainImages = 3; + settings.rendererSettings.numVirtualFrames = 3; + settings.rendererSettings.presentMode = ::vk::PresentModeKHR::eMailbox; + +#ifdef NDEBUG + settings.rendererSettings.useDebugLayers = false; +#else + settings.rendererSettings.useDebugLayers = true; +#endif + + // Initialise main window, and associated renderer. + mainWindow->setup( settings ); +} + + // Initialise and start application + ofRunApp( new ofApp() ); + +} diff --git a/apps/devApps/testVk/src/ofApp.cpp b/apps/devApps/testVk/src/ofApp.cpp new file mode 100644 index 00000000000..74011d5cc5a --- /dev/null +++ b/apps/devApps/testVk/src/ofApp.cpp @@ -0,0 +1,159 @@ +#include "ofApp.h" +#include "ofVkRenderer.h" + +// We keep a shared pointer to the renderer so we don't have to +// fetch it anew every time we need it. +shared_ptr renderer; + +//-------------------------------------------------------------- +void ofApp::setup(){ + ofDisableSetupScreen(); + + renderer = dynamic_pointer_cast( ofGetCurrentRenderer() ); + + { + // Set up a Draw Command which draws a full screen quad. + // + // This command uses the vertex shader to emit vertices, so + // doesn't need any geometry to render. + + of::vk::Shader::Settings shaderSettings; + + shaderSettings.device = renderer->getVkDevice(); + shaderSettings.printDebugInfo = true; + shaderSettings.setSource(::vk::ShaderStageFlagBits::eVertex ,"fullScreenQuad.vert"); + shaderSettings.setSource(::vk::ShaderStageFlagBits::eFragment,"fullScreenQuad.frag"); + + mShaderFullscreen = std::make_shared( shaderSettings ); + + of::vk::GraphicsPipelineState pipeline; + + pipeline.setShader( mShaderFullscreen ); + + // Our full screen quad needs to draw just the back face. This is due to + // how we emit the vertices on the vertex shader. Since this differs from + // the default (back culling) behaviour, we have to set this explicitly. + pipeline.rasterizationState + .setCullMode( ::vk::CullModeFlagBits::eFront ) + .setFrontFace( ::vk::FrontFace::eCounterClockwise ); + + // We don't care about depth testing when drawing the full screen quad. + // It shall always cover the full screen. + pipeline.depthStencilState + .setDepthTestEnable( VK_FALSE ) + .setDepthWriteEnable( VK_FALSE ) + ; + pipeline.blendAttachmentStates[0].blendEnable = VK_TRUE; + + fullscreenQuad.setup( pipeline ); + + // As this draw command issues vertices on the vertex shader + // we must tell it how many vertices to render. + fullscreenQuad.setNumVertices( 3 ); + } + +} + +//-------------------------------------------------------------- +void ofApp::update(){ + ofSetWindowTitle( ofToString( ofGetFrameRate(), 10, ' ' ) ); +} + +//-------------------------------------------------------------- +void ofApp::draw(){ + + // Fetch the default context. This context is automatically + // set up upon app initialisation to draw to the swapchain. + auto & context = renderer->getDefaultContext(); + + // Batch is a light-weight helper object which encapsulates + // a Vulkan Command Buffer. The command buffer is associated + // with the context it has been created from. As long as the + // command buffer lives on the same thread as the context, and + // only uses resources which are either global readonly static, + // or resources which are temporarily allocated though the + // context inside the context's thread, this is thread-safe. + + // setup the main pass renderbatch + // + of::vk::RenderBatch::Settings settings; + + settings + .setContext(context.get()) + .setFramebufferAttachmentsExtent(renderer->getSwapchain()->getWidth(), renderer->getSwapchain()->getHeight()) + .setRenderAreaExtent(renderer->getViewportWidth(), renderer->getViewportHeight()) + .setRenderPass(*renderer->getDefaultRenderpass()) + .addFramebufferAttachment(context->getSwapchainImageView()) + .addClearColorValue(ofFloatColor::blueSteel) + .addFramebufferAttachment(renderer->getDepthStencilImageView()) + .addClearDepthStencilValue({ 1.f,0 }) + ; + + of::vk::RenderBatch batch{ settings }; + + batch.begin(); + batch.draw( fullscreenQuad ); + batch.end(); + + +} + +//-------------------------------------------------------------- +void ofApp::keyPressed(int key){ + +} + +//-------------------------------------------------------------- +void ofApp::keyReleased(int key){ + if ( key == ' ' ){ + // Recompile the full screen shader and + // touch (force implicit re-creation of) any + // associated pipelines. + mShaderFullscreen->compile(); + } +} + +//-------------------------------------------------------------- +void ofApp::mouseMoved(int x, int y ){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseDragged(int x, int y, int button){ + +} + +//-------------------------------------------------------------- +void ofApp::mousePressed(int x, int y, int button){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseReleased(int x, int y, int button){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseEntered(int x, int y){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseExited(int x, int y){ + +} + +//-------------------------------------------------------------- +void ofApp::windowResized(int w, int h){ + +} + +//-------------------------------------------------------------- +void ofApp::gotMessage(ofMessage msg){ + +} + +//-------------------------------------------------------------- +void ofApp::dragEvent(ofDragInfo dragInfo){ + +} diff --git a/apps/devApps/testVk/src/ofApp.h b/apps/devApps/testVk/src/ofApp.h new file mode 100644 index 00000000000..488e409ac83 --- /dev/null +++ b/apps/devApps/testVk/src/ofApp.h @@ -0,0 +1,29 @@ +#pragma once + +#include "ofMain.h" +#include "vk/DrawCommand.h" + +class ofApp : public ofBaseApp{ + + of::vk::DrawCommand fullscreenQuad; + + std::shared_ptr mShaderFullscreen; + + public: + void setup(); + void update(); + void draw(); + + void keyPressed(int key); + void keyReleased(int key); + void mouseMoved(int x, int y ); + void mouseDragged(int x, int y, int button); + void mousePressed(int x, int y, int button); + void mouseReleased(int x, int y, int button); + void mouseEntered(int x, int y); + void mouseExited(int x, int y); + void windowResized(int w, int h); + void dragEvent(ofDragInfo dragInfo); + void gotMessage(ofMessage msg); + +}; diff --git a/apps/devApps/testVk/testVk.sln b/apps/devApps/testVk/testVk.sln new file mode 100644 index 00000000000..3b50316e515 --- /dev/null +++ b/apps/devApps/testVk/testVk.sln @@ -0,0 +1,35 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testVk", "testVk.vcxproj", "{7FD42DF7-442E-479A-BA76-D0022F99702A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "openframeworksLib", "..\..\..\libs\openFrameworksCompiled\project\vs\openframeworksLib.vcxproj", "{5837595D-ACA9-485C-8E76-729040CE4B0B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|Win32.ActiveCfg = Debug|Win32 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|Win32.Build.0 = Debug|Win32 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|x64.ActiveCfg = Debug|x64 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|x64.Build.0 = Debug|x64 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|Win32.ActiveCfg = Release|Win32 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|Win32.Build.0 = Release|Win32 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|x64.ActiveCfg = Release|x64 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|x64.Build.0 = Release|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|Win32.ActiveCfg = Debug|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|Win32.Build.0 = Debug|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|x64.ActiveCfg = Debug|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|x64.Build.0 = Debug|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|Win32.ActiveCfg = Release|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|Win32.Build.0 = Release|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|x64.ActiveCfg = Release|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/apps/devApps/testVk/testVk.vcxproj b/apps/devApps/testVk/testVk.vcxproj new file mode 100644 index 00000000000..76569d3e3a4 --- /dev/null +++ b/apps/devApps/testVk/testVk.vcxproj @@ -0,0 +1,198 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {7FD42DF7-442E-479A-BA76-D0022F99702A} + Win32Proj + testVk + + + + Application + Unicode + v141 + + + Application + Unicode + v141 + + + Application + Unicode + true + v141 + + + Application + Unicode + true + v141 + + + + + + + + + + + + + + + + + + + + + bin\ + obj\$(Configuration)\ + $(ProjectName)_debug + true + true + + + bin\ + obj\$(Configuration)\ + $(ProjectName)_debug + true + true + + + bin\ + obj\$(Configuration)\ + false + + + bin\ + obj\$(Configuration)\ + false + + + + Disabled + EnableFastChecks + %(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + %(AdditionalIncludeDirectories) + CompileAsCpp + + + true + Console + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + Disabled + EnableFastChecks + %(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + %(AdditionalIncludeDirectories) + CompileAsCpp + true + + + true + Console + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + false + %(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + %(AdditionalIncludeDirectories) + CompileAsCpp + true + + + false + false + Console + true + true + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + false + %(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + %(AdditionalIncludeDirectories) + CompileAsCpp + + + false + false + Console + true + true + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + + + + + + + + {5837595d-aca9-485c-8e76-729040ce4b0b} + + + + + /D_DEBUG %(AdditionalOptions) + /D_DEBUG %(AdditionalOptions) + $(OF_ROOT)\libs\openFrameworksCompiled\project\vs + + + + + + + + + \ No newline at end of file diff --git a/apps/devApps/testVk/testVk.vcxproj.filters b/apps/devApps/testVk/testVk.vcxproj.filters new file mode 100644 index 00000000000..c5325387daa --- /dev/null +++ b/apps/devApps/testVk/testVk.vcxproj.filters @@ -0,0 +1,24 @@ + + + + + src + + + src + + + + + {d8376475-7454-4a24-b08a-aac121d3ad6f} + + + + + src + + + + + + diff --git a/apps/devApps/testVkDoubleBuffer/bin/data/.gitkeep b/apps/devApps/testVkDoubleBuffer/bin/data/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/devApps/testVkDoubleBuffer/bin/data/default.frag b/apps/devApps/testVkDoubleBuffer/bin/data/default.frag new file mode 100644 index 00000000000..7e89d9ac8dc --- /dev/null +++ b/apps/devApps/testVkDoubleBuffer/bin/data/default.frag @@ -0,0 +1,28 @@ +#version 420 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +layout (location = 0) in vec4 inColor; +layout (location = 1) in vec3 inNormal; +// layout (location = 2) in vec2 inTexCoord; + +// uniforms (resources) +layout (set = 0, binding = 0) uniform DefaultMatrices +{ + mat4 projectionMatrix; + mat4 modelMatrix; + mat4 viewMatrix; +}; + +layout (location = 0) out vec4 outFragColor; + +void main() +{ + + vec4 normalColor = vec4((inNormal + vec3(1.0)) * vec3(0.5) , 1.0); + vec4 vertexColor = inColor; + + // set the actual fragment color here + outFragColor = vertexColor; +} \ No newline at end of file diff --git a/apps/devApps/testVkDoubleBuffer/bin/data/default.vert b/apps/devApps/testVkDoubleBuffer/bin/data/default.vert new file mode 100644 index 00000000000..fd9983b3c44 --- /dev/null +++ b/apps/devApps/testVkDoubleBuffer/bin/data/default.vert @@ -0,0 +1,42 @@ +#version 420 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// uniforms (resources) +layout (set = 0, binding = 0) uniform DefaultMatrices +{ + mat4 projectionMatrix; + mat4 modelMatrix; + mat4 viewMatrix; +}; // note: if you don't specify a variable name for the block its elements will live in the global namespace. + +layout (set = 0, binding = 1) uniform Style +{ + vec4 globalColor; +} style; + +// inputs (vertex attributes) +layout (location = 0) in vec3 inPos; +//layout (location = 1) in vec4 inColor; +layout (location = 1) in vec3 inNormal; +// layout (location = 2) in vec2 inTexCoord; + +// outputs +layout (location = 0) out vec4 outColor; +layout (location = 1) out vec3 outNormal; +layout (location = 2) out vec2 outTexCoord; + +// we override the built-in fixed function outputs +// to have more control over the SPIR-V code created. +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main() +{ + outNormal = (inverse(transpose( viewMatrix * modelMatrix)) * vec4(inNormal, 0.0)).xyz; + outColor = style.globalColor; + gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(inPos.xyz, 1.0); +} diff --git a/apps/devApps/testVkDoubleBuffer/bin/data/fullScreenQuad.frag b/apps/devApps/testVkDoubleBuffer/bin/data/fullScreenQuad.frag new file mode 100644 index 00000000000..0a66548118a --- /dev/null +++ b/apps/devApps/testVkDoubleBuffer/bin/data/fullScreenQuad.frag @@ -0,0 +1,17 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// inputs +layout (location = 0) in vec2 inTexCoord; + +// outputs +layout (location = 0) out vec4 outFragColor; + +void main(){ + + float dist = length(inTexCoord - vec2(0.5)) / sqrt(2.); + + outFragColor = vec4(vec3(1.0-dist), 1.0); +} \ No newline at end of file diff --git a/apps/devApps/testVkDoubleBuffer/bin/data/fullScreenQuad.vert b/apps/devApps/testVkDoubleBuffer/bin/data/fullScreenQuad.vert new file mode 100644 index 00000000000..e842b212d64 --- /dev/null +++ b/apps/devApps/testVkDoubleBuffer/bin/data/fullScreenQuad.vert @@ -0,0 +1,23 @@ +#version 450 core + +// This shader built after a technique introduced in: +// http://www.saschawillems.de/?page_id=2122 + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// outputs +layout (location = 0) out vec2 outTexCoord; + +// we override the built-in fixed function outputs +// to have more control over the SPIR-V code created. +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main() +{ + outTexCoord = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + gl_Position = vec4(outTexCoord * 2.0f + -1.0f, 0.0f, 1.0f); +} diff --git a/apps/devApps/testVkDoubleBuffer/bin/data/textured.frag b/apps/devApps/testVkDoubleBuffer/bin/data/textured.frag new file mode 100644 index 00000000000..08e3c103e52 --- /dev/null +++ b/apps/devApps/testVkDoubleBuffer/bin/data/textured.frag @@ -0,0 +1,21 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + + +layout (set = 0, binding = 1) uniform sampler2D tex_0; + +layout (location = 0) in vec2 inTexCoord; + +layout (location = 0) out vec4 outFragColor; + +void main() +{ + + vec4 sampledColor = texture(tex_0, inTexCoord); + outFragColor = sampledColor; + + // outFragColor = vec4(inTexCoord, 0.f, 1.f); + +} \ No newline at end of file diff --git a/apps/devApps/testVkDoubleBuffer/bin/data/textured.vert b/apps/devApps/testVkDoubleBuffer/bin/data/textured.vert new file mode 100644 index 00000000000..22e5cda6fd8 --- /dev/null +++ b/apps/devApps/testVkDoubleBuffer/bin/data/textured.vert @@ -0,0 +1,33 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// uniforms (resources) +layout (set = 0, binding = 0) uniform DefaultMatrices +{ + mat4 projectionMatrix; + mat4 modelMatrix; + mat4 viewMatrix; +}; + +// inputs (vertex attributes) +layout (location = 0) in vec3 inPos; +layout (location = 1) in vec2 inTexCoord; +layout (location = 2) in vec3 inNormal; + +// outputs +layout (location = 0) out vec2 outTexCoord; + +// we override the built-in fixed function outputs +// to have more control over the SPIR-V code created. +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main() +{ + outTexCoord = vec2(inTexCoord.x,1.0 -inTexCoord.y); + gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(inPos.xyz, 1.0); +} diff --git a/apps/devApps/testVkDoubleBuffer/src/main.cpp b/apps/devApps/testVkDoubleBuffer/src/main.cpp new file mode 100644 index 00000000000..2d9d5f72ab1 --- /dev/null +++ b/apps/devApps/testVkDoubleBuffer/src/main.cpp @@ -0,0 +1,40 @@ +#include "ofMain.h" +#include "ofApp.h" + +int main(){ + // Do basic initialisation (mostly setup timers, and randseed) + ofInit(); + + auto consoleLogger = new ofConsoleLoggerChannel(); + ofSetLoggerChannel( std::shared_ptr( consoleLogger, []( ofBaseLoggerChannel * lhs){} ) ); + + // Create a new window + auto mainWindow = std::make_shared(); + //auto mainWindow = std::make_shared(); + + // Store main window in mainloop + ofGetMainLoop()->addWindow( mainWindow ); + + { + ofVkWindowSettings settings; + + settings.rendererSettings.setVkVersion( 1, 0, 39 ); + settings.rendererSettings.numSwapchainImages = 3; + settings.rendererSettings.numVirtualFrames = 3; + settings.rendererSettings.presentMode = ::vk::PresentModeKHR::eMailbox; + settings.rendererSettings.requestedQueues = { ::vk::QueueFlagBits::eGraphics }; + +#ifdef NDEBUG + settings.rendererSettings.useDebugLayers = false; +#else + settings.rendererSettings.useDebugLayers = true; +#endif + + // Initialise main window, and associated renderer. + mainWindow->setup( settings ); +} + + // Initialise and start application + ofRunApp( new ofApp() ); + +} diff --git a/apps/devApps/testVkDoubleBuffer/src/ofApp.cpp b/apps/devApps/testVkDoubleBuffer/src/ofApp.cpp new file mode 100644 index 00000000000..cf4c338a01f --- /dev/null +++ b/apps/devApps/testVkDoubleBuffer/src/ofApp.cpp @@ -0,0 +1,468 @@ +#include "ofApp.h" +#include "ofVkRenderer.h" + +// We keep a shared pointer to the renderer so we don't have to +// fetch it anew every time we need it. +shared_ptr renderer; + +const glm::mat4x4 cClipMatrix { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, -1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.5f, 0.0f, + 0.0f, 0.0f, 0.5f, 1.0f +}; + + +//-------------------------------------------------------------- + +void ofApp::setup(){ + ofDisableSetupScreen(); + + renderer = dynamic_pointer_cast( ofGetCurrentRenderer() ); + setupPrepass(); + setupDrawCommands(); + + mMeshIco = std::make_shared(); + *mMeshIco = ofBoxPrimitive(100,100,100,1,1,1).getMesh(); + + mMeshIco->clearTexCoords(); + + mMeshPlane = std::make_shared( ofMesh::plane( 512, 256, 2, 2, OF_PRIMITIVE_TRIANGLES ) ); + + setupMeshL(); + +} + +//-------------------------------------------------------------- + +void ofApp::setupPrepass(){ + + auto & device = renderer->getVkDevice(); + + // set dimensions for aux render target + mPrepassRect.setExtent( { 512, 256 } ); + mPrepassRect.setOffset( { 0, 0 } ); + + // Create a Renderpass which defines dependencies, + // attachments, initialisation behaviour, and colour + // formats for the subpass. + { + std::array attachments; + + attachments[0] // color attachment + .setFormat( ::vk::Format::eR8G8B8A8Unorm ) + .setSamples( ::vk::SampleCountFlagBits::e1 ) + .setLoadOp( ::vk::AttachmentLoadOp::eClear ) // <-- try setting this to vk::AttachmentLoadOp::eDontCare and see what happens! + .setStoreOp( ::vk::AttachmentStoreOp::eStore ) + .setStencilLoadOp( ::vk::AttachmentLoadOp::eDontCare ) + .setStencilStoreOp( ::vk::AttachmentStoreOp::eDontCare ) + .setInitialLayout( ::vk::ImageLayout::eUndefined ) + .setFinalLayout( ::vk::ImageLayout::eShaderReadOnlyOptimal ) + ; + + // Define 2 attachments, and tell us what layout to expect these to be in. + // Index references attachments from above. + + vk::AttachmentReference colorReference{ 0, ::vk::ImageLayout::eColorAttachmentOptimal }; + + vk::SubpassDescription subpassDescription; + subpassDescription + .setPipelineBindPoint( ::vk::PipelineBindPoint::eGraphics ) + .setColorAttachmentCount( 1 ) + .setPColorAttachments( &colorReference ) + .setPDepthStencilAttachment( nullptr ) + ; + + // Define 2 dependencies for subpass 0 + + std::array dependencies; + dependencies[0] + .setSrcSubpass( VK_SUBPASS_EXTERNAL ) // producer + .setDstSubpass( 0 ) // consumer + .setSrcStageMask( ::vk::PipelineStageFlagBits::eBottomOfPipe ) + .setDstStageMask( ::vk::PipelineStageFlagBits::eColorAttachmentOutput ) + .setSrcAccessMask( ::vk::AccessFlagBits::eMemoryRead ) + .setDstAccessMask( ::vk::AccessFlagBits::eColorAttachmentWrite ) + .setDependencyFlags( ::vk::DependencyFlagBits::eByRegion ) + ; + dependencies[1] + .setSrcSubpass( 0 ) // producer + .setDstSubpass( VK_SUBPASS_EXTERNAL ) // consumer + .setSrcStageMask( ::vk::PipelineStageFlagBits::eColorAttachmentOutput ) + .setDstStageMask( ::vk::PipelineStageFlagBits::eTopOfPipe ) + .setSrcAccessMask( ::vk::AccessFlagBits::eColorAttachmentWrite ) + .setDstAccessMask( ::vk::AccessFlagBits::eMemoryRead ) + .setDependencyFlags( vk::DependencyFlagBits::eByRegion ) + ; + + // Define 1 renderpass with 1 subpass + + vk::RenderPassCreateInfo renderPassCreateInfo; + renderPassCreateInfo + .setAttachmentCount( (uint32_t)attachments.size() ) + .setPAttachments( attachments.data() ) + .setSubpassCount( 1 ) + .setPSubpasses( &subpassDescription ) + .setDependencyCount( (uint32_t)dependencies.size() ) + .setPDependencies( dependencies.data() ); + + auto renderPass = device.createRenderPass( renderPassCreateInfo ); + + // We put renderpass into a shared_ptr so we can be sure it will get destroyed on app teardown + mPrepassRenderPass = std::shared_ptr<::vk::RenderPass>( new ::vk::RenderPass( renderPass ), [d = device]( ::vk::RenderPass* rp ){ + d.destroyRenderPass( *rp ); + delete rp; + } ); + } + + // ---- Allocate images for context to render into + { + // we allocate 2 images, so that we could ping-pong between + // rendertargets. In this example, we're not really + // making full use of this, to keep things simple. + + size_t numImages = 2; + + of::vk::ImageAllocator::Settings allocatorSettings; + + allocatorSettings + .setRendererProperties( renderer->getVkRendererProperties() ) + .setImageTiling( vk::ImageTiling::eOptimal ) + .setImageUsageFlags( vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled ) + .setMemFlags( vk::MemoryPropertyFlagBits::eDeviceLocal ) + .setSize( mPrepassRect.extent.width * mPrepassRect.extent.height * 4 * numImages ) + ; + + mImageAllocator.setup(allocatorSettings); + + ::vk::ImageCreateInfo imageCreateInfo; + imageCreateInfo + .setImageType( ::vk::ImageType::e2D ) + .setFormat( ::vk::Format::eR8G8B8A8Unorm ) + .setExtent( { mPrepassRect.extent.width, mPrepassRect.extent.height, 1 } ) + .setMipLevels( 1 ) + .setArrayLayers( 1 ) + .setSamples( ::vk::SampleCountFlagBits::e1 ) + .setTiling( ::vk::ImageTiling::eOptimal ) + .setUsage( ::vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled ) + .setSharingMode( ::vk::SharingMode::eExclusive ) + .setQueueFamilyIndexCount( 0 ) + .setPQueueFamilyIndices( nullptr ) + .setInitialLayout( ::vk::ImageLayout::eUndefined ) + ; + + ::vk::ImageViewCreateInfo imageViewCreateInfo; + + // Todo: these elements need to be destroyed when the app quits. + for ( size_t i = 0; i != numImages; ++i ){ + auto img = device.createImage( imageCreateInfo ); + imageViewCreateInfo + .setImage( img ) + .setViewType( ::vk::ImageViewType::e2D ) + .setFormat( imageCreateInfo.format ) + .setComponents( {} ) // identity + .setSubresourceRange( { ::vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } ) + ; + + ::vk::DeviceSize offset; + mImageAllocator.allocate( mPrepassRect.extent.width * mPrepassRect.extent.height * 4, offset ); + device.bindImageMemory( img, mImageAllocator.getDeviceMemory(), offset ); + + auto view = device.createImageView( imageViewCreateInfo ); + + mTargetImages[i].image = img; + mTargetImages[i].view = view; + + of::vk::Texture::Settings textureSettings; + textureSettings + .setDevice(device) + .setImage(img) + ; + + mTexture[i].setup(textureSettings); + } + } + + + mCamPrepass.setupPerspective( false, 60, 0.1, 500 ); + mCamPrepass.setGlobalPosition( 0, 0, mCam.getImagePlaneDistance( { 0,0,512,256 } ) ); + mCamPrepass.lookAt( { 0,0,0 }, { 0,1,0 } ); + + mCam.setupPerspective( false, 60 ); + mCam.setPosition( { 0,0, mCam.getImagePlaneDistance() } ); + mCam.lookAt( { 0,0,0 } ); + mCam.setEvents( ofEvents() ); + +} + +//-------------------------------------------------------------- + +void ofApp::setupDrawCommands(){ + + of::vk::Shader::Settings shaderSettings; + + shaderSettings.device = renderer->getVkDevice(); + shaderSettings.printDebugInfo = true; + + shaderSettings.setSource( ::vk::ShaderStageFlagBits::eVertex, "fullScreenQuad.vert" ); + shaderSettings.setSource( ::vk::ShaderStageFlagBits::eFragment, "fullScreenQuad.frag" ); + mShaderFullscreen = std::make_shared( shaderSettings ); + + shaderSettings.setSource( ::vk::ShaderStageFlagBits::eVertex, "default.vert" ); + shaderSettings.setSource( ::vk::ShaderStageFlagBits::eFragment, "default.frag" ); + auto mShaderDefault = std::make_shared( shaderSettings ); + + shaderSettings.setSource(::vk::ShaderStageFlagBits::eVertex , "textured.vert"); + shaderSettings.setSource(::vk::ShaderStageFlagBits::eFragment, "textured.frag"); + auto mShaderTextured = std::make_shared( shaderSettings ); + + { + // Set up a Draw Command which draws a full screen quad. + // + // This command uses the vertex shader to emit vertices, so + // doesn't need any geometry to render. + + of::vk::GraphicsPipelineState pipeline; + pipeline.setShader( mShaderFullscreen ); + + // Our full screen quad needs to draw just the back face. This is due to + // how we emit the vertices on the vertex shader. Since this differs from + // the default (back culling) behaviour, we have to set this explicitly. + pipeline.rasterizationState + .setCullMode( ::vk::CullModeFlagBits::eFront ) + .setFrontFace( ::vk::FrontFace::eCounterClockwise ); + + // We don't care about depth testing when drawing the full screen quad. + // It shall always cover the full screen. + pipeline.depthStencilState + .setDepthTestEnable( VK_FALSE ) + .setDepthWriteEnable( VK_FALSE ) + ; + pipeline.blendAttachmentStates[0].blendEnable = VK_TRUE; + + const_cast( fullscreenQuad).setup( pipeline ); + + // As this draw command issues vertices on the vertex shader + // we must tell it how many vertices to render. + const_cast( fullscreenQuad).setNumVertices( 3 ); + } + + { + // Draw Command which draws geometry as outlines + + of::vk::GraphicsPipelineState pipeline; + pipeline.setShader( mShaderDefault ); + + pipeline.depthStencilState + .setDepthTestEnable( VK_TRUE ) + .setDepthWriteEnable( VK_TRUE ) + ; + + pipeline.inputAssemblyState.setTopology( ::vk::PrimitiveTopology::eTriangleList ); + pipeline.rasterizationState.setPolygonMode( ::vk::PolygonMode::eLine ); + pipeline.blendAttachmentStates[0] + .setBlendEnable( VK_TRUE ) + ; + + const_cast( outlinesDraw ).setup( pipeline ); + } + + { + // Draw command which draws textured geometry + + of::vk::GraphicsPipelineState pipeline; + pipeline.setShader( mShaderTextured ); + + pipeline.rasterizationState.setCullMode( ::vk::CullModeFlagBits::eBack ); + pipeline.rasterizationState.setFrontFace( ::vk::FrontFace::eCounterClockwise ); + pipeline.depthStencilState + .setDepthTestEnable( VK_TRUE ) + .setDepthWriteEnable( VK_TRUE ) + ; + pipeline.blendAttachmentStates[0] + .setBlendEnable(VK_TRUE); + + const_cast( drawTextured ).setup( pipeline ); + } +} + +//-------------------------------------------------------------- + +void ofApp::update(){ + ofSetWindowTitle( ofToString( ofGetFrameRate(), 10, ' ' ) ); +} + +//-------------------------------------------------------------- + +void ofApp::draw(){ + + static uint32_t pingPong = 0; + auto & context = renderer->getDefaultContext(); + + { // prepass + + // setup renderbatch for pre-pass + // + of::vk::RenderBatch::Settings settings; + settings + .setContext(renderer->getDefaultContext().get()) + .setFramebufferAttachmentsExtent(mPrepassRect.extent.width, mPrepassRect.extent.height) + .setRenderArea(mPrepassRect) + .setRenderPass(*mPrepassRenderPass) + .addFramebufferAttachment(mTargetImages[pingPong].view) // << this image is where the result of our prepass will be stored + .addClearColorValue( ofFloatColor::bisque ) // add a clear value for the framebuffer attachment + ; + + of::vk::RenderBatch prepass{ settings }; + + auto viewMatrix = mCamPrepass.getModelViewMatrix(); + auto projectionMatrix = cClipMatrix * mCamPrepass.getProjectionMatrix( { 0, 0, float( mPrepassRect.extent.width ), float( mPrepassRect.extent.height ) } ); + + ofMatrix4x4 modelMatrix = glm::rotate( float( TWO_PI * ( ( ofGetFrameNum() % 360 ) / 360.f ) ), glm::vec3( { 0.f, 1.f, 1.f } ) ); + + auto meshDraw = outlinesDraw; + + ofFloatColor col = ofColor::white; + col.lerp( ofColor::blue, 0.5 + 0.5 * sinf(TWO_PI * (ofGetFrameNum() % 360 ) / 360.f )); + + meshDraw + .setUniform( "projectionMatrix", projectionMatrix ) + .setUniform( "viewMatrix", viewMatrix ) + .setUniform( "modelMatrix", modelMatrix ) + .setUniform( "globalColor", col ) + .setMesh(mMeshIco) + .setDrawMethod( of::vk::DrawCommand::DrawMethod::eIndexed ) + ; + + prepass.begin(); + prepass.draw( meshDraw ); + prepass.end(); + + } + + { // main pass + + // renderbatch for main pass + // + of::vk::RenderBatch::Settings settings; + settings + .setContext(renderer->getDefaultContext().get()) + .setFramebufferAttachmentsExtent(renderer->getSwapchain()->getWidth(), renderer->getSwapchain()->getHeight()) + .setRenderArea(::vk::Rect2D( {}, { uint32_t( renderer->getViewportWidth() ), uint32_t( renderer->getViewportHeight() ) } )) + .setRenderPass(*renderer->getDefaultRenderpass()) + .addFramebufferAttachment( context->getSwapchainImageView() ) // image attachment + .addClearColorValue( ( ::vk::ClearColorValue& )ofFloatColor::blueSteel ) // clear color for image + .addFramebufferAttachment( renderer->getDepthStencilImageView() ) // depth stencil attachment + .addClearDepthStencilValue( {1.f,0} ) // clear value for depth stencil + ; + + of::vk::RenderBatch batch{ settings }; + + auto viewMatrix = mCam.getModelViewMatrix(); + auto projectionMatrix = cClipMatrix * mCam.getProjectionMatrix(); + + auto texturedRect = drawTextured; + texturedRect + .setUniform( "projectionMatrix", projectionMatrix ) + .setUniform( "viewMatrix", viewMatrix ) + .setUniform( "modelMatrix", glm::mat4() ) + .setTexture( "tex_0", mTexture[(pingPong + 1 ) % 2] ) + .setMesh( mMeshPlane ) + .setDrawMethod( of::vk::DrawCommand::DrawMethod::eIndexed ) + ; + + batch.begin(); + batch + .draw( fullscreenQuad ) + .draw( texturedRect ) // draw result from previous render pass onto screen + ; + batch.end(); + + } + + // Note that ping-pong in this case doesn't really do anything, + // as the way we have setup our renderpasses, their dependencies (outside writes + // must have finished before reading inside the renderpass) warrant that + // the result of our prepass is available for the main pass to draw. + pingPong = ( pingPong + 1) % 2; +} + +//-------------------------------------------------------------- +void ofApp::exit(){ + // cleanup + + auto & device = renderer->getVkDevice(); + + device.waitIdle(); + + for ( auto& i : mTargetImages ){ + device.destroyImageView( i.view ); + device.destroyImage( i.image ); + } + + for (auto &t : mTexture) { + t.reset(); + } + +} + +//-------------------------------------------------------------- + +void ofApp::keyPressed(int key){ + +} + +//-------------------------------------------------------------- +void ofApp::keyReleased(int key){ + if ( key == ' ' ){ + // Recompile the full screen shader and + // touch (force implicit re-creation of) any + // associated pipelines. + mShaderFullscreen->compile(); + } +} + +//-------------------------------------------------------------- +void ofApp::mouseMoved(int x, int y ){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseDragged(int x, int y, int button){ + +} + +//-------------------------------------------------------------- +void ofApp::mousePressed(int x, int y, int button){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseReleased(int x, int y, int button){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseEntered(int x, int y){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseExited(int x, int y){ + +} + +//-------------------------------------------------------------- +void ofApp::windowResized(int w, int h){ + mCam.setControlArea( { 0,0,float( w ),float( h ) } ); +} + +//-------------------------------------------------------------- +void ofApp::gotMessage(ofMessage msg){ + +} + +//-------------------------------------------------------------- +void ofApp::dragEvent(ofDragInfo dragInfo){ + +} diff --git a/apps/devApps/testVkDoubleBuffer/src/ofApp.h b/apps/devApps/testVkDoubleBuffer/src/ofApp.h new file mode 100644 index 00000000000..27edc5715d0 --- /dev/null +++ b/apps/devApps/testVkDoubleBuffer/src/ofApp.h @@ -0,0 +1,84 @@ +#pragma once + +#include "ofMain.h" +#include "vk/DrawCommand.h" +#include "vk/ImageAllocator.h" +#include "vk/HelperTypes.h" +#include "vk/Context.h" + +class ofApp : public ofBaseApp{ + + const of::vk::DrawCommand fullscreenQuad; + const of::vk::DrawCommand outlinesDraw; + const of::vk::DrawCommand drawTextured; + + of::vk::ImageAllocator mImageAllocator; + + std::shared_ptr mShaderFullscreen; + + struct ImageWithView + { + ::vk::Image image; + ::vk::ImageView view; + }; + + std::array mTargetImages; + std::array mTexture; + + ofEasyCam mCam; + ofCamera mCamPrepass; + + std::shared_ptr mMeshIco; + std::shared_ptr mMeshPlane; + std::shared_ptr mMeshL; + + std::shared_ptr<::vk::RenderPass> mPrepassRenderPass; + vk::Rect2D mPrepassRect; // dimensions for prepass render targets + +public: + void setup(); + void update(); + void draw(); + void exit() override; + + void setupPrepass(); + void setupDrawCommands(); + + void keyPressed(int key); + void keyReleased(int key); + void mouseMoved(int x, int y ); + void mouseDragged(int x, int y, int button); + void mousePressed(int x, int y, int button); + void mouseReleased(int x, int y, int button); + void mouseEntered(int x, int y); + void mouseExited(int x, int y); + void windowResized(int w, int h); + void dragEvent(ofDragInfo dragInfo); + void gotMessage(ofMessage msg); + + void setupMeshL(){ + // Horizontally elongated "L___" shape + mMeshL = make_shared(); + vector vert{ + { 0.f,0.f,0.f }, + { 20.f,20.f,0.f }, + { 0.f,100.f,0.f }, + { 20.f,100.f,0.f }, + { 200.f,0.f,0.f }, + { 200.f,20.f,0.f } + }; + + vector idx{ + 0, 1, 2, + 1, 3, 2, + 0, 4, 1, + 1, 4, 5, + }; + + vector norm( vert.size(), { 0, 0, 1.f } ); + + mMeshL->addVertices( vert ); + mMeshL->addNormals( norm ); + mMeshL->addIndices( idx ); + } +}; diff --git a/apps/devApps/testVkDoubleBuffer/testVkDoubleBuffer.sln b/apps/devApps/testVkDoubleBuffer/testVkDoubleBuffer.sln new file mode 100644 index 00000000000..1e9053c20cf --- /dev/null +++ b/apps/devApps/testVkDoubleBuffer/testVkDoubleBuffer.sln @@ -0,0 +1,35 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testVkDoubleBuffer", "testVkDoubleBuffer.vcxproj", "{7FD42DF7-442E-479A-BA76-D0022F99702A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "openframeworksLib", "..\..\..\libs\openFrameworksCompiled\project\vs\openframeworksLib.vcxproj", "{5837595D-ACA9-485C-8E76-729040CE4B0B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|Win32.ActiveCfg = Debug|Win32 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|Win32.Build.0 = Debug|Win32 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|x64.ActiveCfg = Debug|x64 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|x64.Build.0 = Debug|x64 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|Win32.ActiveCfg = Release|Win32 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|Win32.Build.0 = Release|Win32 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|x64.ActiveCfg = Release|x64 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|x64.Build.0 = Release|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|Win32.ActiveCfg = Debug|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|Win32.Build.0 = Debug|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|x64.ActiveCfg = Debug|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|x64.Build.0 = Debug|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|Win32.ActiveCfg = Release|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|Win32.Build.0 = Release|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|x64.ActiveCfg = Release|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/apps/devApps/testVkDoubleBuffer/testVkDoubleBuffer.vcxproj b/apps/devApps/testVkDoubleBuffer/testVkDoubleBuffer.vcxproj new file mode 100644 index 00000000000..a95af5866ef --- /dev/null +++ b/apps/devApps/testVkDoubleBuffer/testVkDoubleBuffer.vcxproj @@ -0,0 +1,198 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {82612694-99EE-41CD-A72D-956B51BE6B0B} + Win32Proj + testVkDoubleBuffer + + + + Application + Unicode + v141 + + + Application + Unicode + v141 + + + Application + Unicode + true + v141 + + + Application + Unicode + true + v141 + + + + + + + + + + + + + + + + + + + + + bin\ + obj\$(Configuration)\ + $(ProjectName)_debug + true + true + + + bin\ + obj\$(Configuration)\ + $(ProjectName)_debug + true + true + + + bin\ + obj\$(Configuration)\ + false + + + bin\ + obj\$(Configuration)\ + false + + + + Disabled + EnableFastChecks + %(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + %(AdditionalIncludeDirectories);src + CompileAsCpp + + + true + Console + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + Disabled + EnableFastChecks + %(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + %(AdditionalIncludeDirectories);src + CompileAsCpp + true + + + true + Console + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + false + %(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + %(AdditionalIncludeDirectories);src + CompileAsCpp + true + + + false + false + Console + true + true + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + false + %(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + %(AdditionalIncludeDirectories);src + CompileAsCpp + + + false + false + Console + true + true + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + + + + + + + + {5837595d-aca9-485c-8e76-729040ce4b0b} + + + + + /D_DEBUG %(AdditionalOptions) + /D_DEBUG %(AdditionalOptions) + $(OF_ROOT)\libs\openFrameworksCompiled\project\vs + + + + + + + + + \ No newline at end of file diff --git a/apps/devApps/testVkDoubleBuffer/testVkDoubleBuffer.vcxproj.filters b/apps/devApps/testVkDoubleBuffer/testVkDoubleBuffer.vcxproj.filters new file mode 100644 index 00000000000..0da4021c023 --- /dev/null +++ b/apps/devApps/testVkDoubleBuffer/testVkDoubleBuffer.vcxproj.filters @@ -0,0 +1,33 @@ + + + + + src + + + src + + + src + + + src + + + + + {d8376475-7454-4a24-b08a-aac121d3ad6f} + + + + + src + + + src + + + + + + diff --git a/apps/devApps/testVkFun/addons.make b/apps/devApps/testVkFun/addons.make new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/devApps/testVkFun/bin/data/.gitkeep b/apps/devApps/testVkFun/bin/data/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/devApps/testVkFun/bin/data/compute.glsl b/apps/devApps/testVkFun/bin/data/compute.glsl new file mode 100644 index 00000000000..18eb8c9c157 --- /dev/null +++ b/apps/devApps/testVkFun/bin/data/compute.glsl @@ -0,0 +1,35 @@ +#version 450 + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +struct Particle { + vec2 pos; + vec2 vel; + vec4 result; +}; + + +// Binding 0 : Position storage buffer +layout(std430, binding = 0) buffer ParticleBuf +{ + Particle particles[ ]; +}; + +layout(set=0, binding=1) uniform Parameters { + uint flipFlop; // will be either 0 or 1 +}; + + +layout (local_size_x = 1, local_size_y = 1) in; + +void main(){ + // uint srcIndex = (flipFlop ) * gl_GlobalInvocationID.x; + // uint dstIndex = ((flipFlop + 1) % 2) * gl_GlobalInvocationID.x; + uint srcIndex = (flipFlop ) ; + uint dstIndex = ((flipFlop + 1) % 2) ; + + particles[dstIndex].pos.xy = particles[srcIndex].pos + particles[srcIndex].vel; + + +} \ No newline at end of file diff --git a/apps/devApps/testVkFun/bin/data/default.frag b/apps/devApps/testVkFun/bin/data/default.frag new file mode 100644 index 00000000000..84dc65c7a0c --- /dev/null +++ b/apps/devApps/testVkFun/bin/data/default.frag @@ -0,0 +1,43 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// uniforms (resources) +layout (set = 0, binding = 0) uniform DefaultMatrices +{ + mat4 projectionMatrix; + mat4 modelMatrix; + mat4 viewMatrix; +}; + +layout (std430, set = 0, binding = 1) buffer colorLayout +{ + vec4 colourList[]; +}; + +layout (location = 0) in vec4 inPosition; +layout (location = 1) flat in vec3 inNormal; + +layout (location = 0) out vec4 outFragColor; + +// include external glsl shader source file +#include "lighting.glsl" + +void main() +{ + + // We do light calculations in eye==camera space + vec4 lightPos = viewMatrix * vec4(-2000,100,2000,1); + + vec3 normal = normalize(inNormal); + + vec3 l = normalize(lightPos - inPosition).xyz; + + vec3 normalColor = (inNormal + vec3(1.0)) * vec3(0.5); + // outFragColor = vec4(normalColor,1); + + vec3 plainColor = colourList[1].rgb; + + outFragColor = vec4(plainColor * lambert(l,normal), 1); +} \ No newline at end of file diff --git a/apps/devApps/testVkFun/bin/data/default.vert b/apps/devApps/testVkFun/bin/data/default.vert new file mode 100644 index 00000000000..dbb8de28bbb --- /dev/null +++ b/apps/devApps/testVkFun/bin/data/default.vert @@ -0,0 +1,46 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// uniforms (resources) +layout (set = 0, binding = 0) uniform DefaultMatrices +{ + mat4 projectionMatrix; + mat4 modelMatrix; + mat4 viewMatrix; + +}; +// note: if you don't specify a variable name for the block, +// its elements will live in the global namespace. +// layout (set = 0, binding = 1) uniform Style +// { +// vec4 globalColor; +// } style; + +layout (std430, set = 0, binding = 1) buffer colorLayout +{ + vec4 colourList[]; +}; + +// inputs (vertex attributes) +layout (location = 0) in vec3 inPos; +layout (location = 1) in vec3 inNormal; + +// outputs +layout (location = 0) out vec4 outPosition; +layout (location = 1) flat out vec3 outNormal; + +// we override the built-in fixed function outputs +// to have more control over the SPIR-V code created. +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main() +{ + outNormal = ((transpose(inverse( viewMatrix * modelMatrix)) * vec4(inNormal,0.0))).xyz; + outPosition = viewMatrix * modelMatrix * vec4(inPos.xyz, 1.0); + gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(inPos.xyz, 1.0); +} diff --git a/apps/devApps/testVkFun/bin/data/fullScreenQuad.frag b/apps/devApps/testVkFun/bin/data/fullScreenQuad.frag new file mode 100644 index 00000000000..7e7fdc07a8d --- /dev/null +++ b/apps/devApps/testVkFun/bin/data/fullScreenQuad.frag @@ -0,0 +1,14 @@ +#version 420 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// inputs +layout (location = 0) in vec2 inTexCoord; + +// outputs +layout (location = 0) out vec4 outFragColor; + +void main(){ + outFragColor = vec4(inTexCoord, 0, 0.75); +} \ No newline at end of file diff --git a/apps/devApps/testVkFun/bin/data/fullScreenQuad.vert b/apps/devApps/testVkFun/bin/data/fullScreenQuad.vert new file mode 100644 index 00000000000..88d9b095099 --- /dev/null +++ b/apps/devApps/testVkFun/bin/data/fullScreenQuad.vert @@ -0,0 +1,23 @@ +#version 420 core + +// This shader built after a technique introduced in: +// http://www.saschawillems.de/?page_id=2122 + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// outputs +layout (location = 0) out vec2 outTexCoord; + +// we override the built-in fixed function outputs +// to have more control over the SPIR-V code created. +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main() +{ + outTexCoord = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + gl_Position = vec4(outTexCoord * 2.0f + -1.0f, 0.0f, 1.0f); +} diff --git a/apps/devApps/testVkFun/bin/data/ico-m.ply b/apps/devApps/testVkFun/bin/data/ico-m.ply new file mode 100644 index 00000000000..a4bf7f233c8 --- /dev/null +++ b/apps/devApps/testVkFun/bin/data/ico-m.ply @@ -0,0 +1,719 @@ +ply +format ascii 1.0 +comment Created by Blender 2.77 (sub 0) - www.blender.org, source file: '' +element vertex 500 +property float x +property float y +property float z +property float nx +property float ny +property float nz +element face 206 +property list uchar uint vertex_indices +end_header +0.000000 0.000000 -100.000000 0.102381 -0.315090 -0.943524 +42.532269 -30.901140 -85.065422 0.102381 -0.315090 -0.943524 +-16.245556 -49.999527 -85.065445 0.102381 -0.315090 -0.943524 +72.360733 -52.572529 -44.721951 0.700224 -0.268032 -0.661699 +42.532269 -30.901140 -85.065422 0.700224 -0.268032 -0.661699 +85.064789 0.000000 -52.573593 0.700224 -0.268032 -0.661699 +0.000000 0.000000 -100.000000 -0.268034 -0.194736 -0.943523 +-16.245556 -49.999527 -85.065445 -0.268034 -0.194736 -0.943523 +-52.572979 0.000000 -85.065170 -0.268034 -0.194736 -0.943523 +0.000000 0.000000 -100.000000 -0.268034 0.194736 -0.943523 +-52.572979 0.000000 -85.065170 -0.268034 0.194736 -0.943523 +-16.245556 49.999527 -85.065445 -0.268034 0.194736 -0.943523 +42.532269 30.901140 -85.065422 0.291569 0.897343 0.331306 +-11.686449 64.030731 -127.081314 0.291569 0.897343 0.331306 +-16.245556 49.999527 -85.065445 0.291569 0.897343 0.331306 +72.360733 -52.572529 -44.721951 0.904989 -0.268032 -0.330384 +85.064789 0.000000 -52.573593 0.904989 -0.268032 -0.330384 +95.105782 -30.901262 0.000000 0.904989 -0.268032 -0.330384 +-27.638802 -85.064926 -44.721985 0.024747 -0.943521 -0.330386 +26.286882 -80.901161 -52.573765 0.024747 -0.943521 -0.330386 +0.000000 -99.999992 0.000000 0.024747 -0.943521 -0.330386 +-89.442619 0.000000 -44.721561 -0.889697 -0.315095 -0.330385 +-68.818939 -49.999695 -52.573620 -0.889697 -0.315095 -0.330385 +-95.105782 -30.901262 0.000000 -0.889697 -0.315095 -0.330385 +-27.638802 85.064926 -44.721985 -0.574602 0.748784 -0.330388 +-68.818939 49.999695 -52.573620 -0.574602 0.748784 -0.330388 +-58.778561 80.901672 0.000000 -0.574602 0.748784 -0.330388 +72.360733 52.572529 -44.721951 0.534576 0.777865 -0.330387 +26.286882 80.901161 -52.573765 0.534576 0.777865 -0.330387 +58.778561 80.901672 0.000000 0.534576 0.777865 -0.330387 +95.105782 -30.901262 0.000000 0.101635 -0.073842 0.992077 +105.568504 -114.896370 -7.323733 0.101635 -0.073842 0.992077 +141.895737 -64.895966 -7.323733 0.101635 -0.073842 0.992077 +-27.638802 -85.064926 -44.721985 -0.306569 -0.943521 -0.125629 +0.000000 -99.999992 0.000000 -0.306569 -0.943521 -0.125629 +-58.778561 -80.901672 0.000000 -0.306569 -0.943521 -0.125629 +-89.442619 0.000000 -44.721561 -0.992077 -0.000000 -0.125628 +-95.105782 -30.901262 0.000000 -0.992077 -0.000000 -0.125628 +-95.105782 30.901262 0.000000 -0.992077 -0.000000 -0.125628 +-27.638802 85.064926 -44.721985 -0.306569 0.943521 -0.125629 +-58.778561 80.901672 0.000000 -0.306569 0.943521 -0.125629 +0.000000 99.999992 0.000000 -0.306569 0.943521 -0.125629 +95.105782 30.901262 0.000000 0.101635 0.073842 0.992078 +122.723297 127.359985 -10.008863 0.101635 0.073842 0.992078 +58.778561 80.901672 0.000000 0.101635 0.073842 0.992078 +16.245556 -49.999527 85.065437 0.000000 0.000000 0.000000 +68.818939 -49.999695 52.573620 0.000000 0.000000 0.000000 +68.818939 -49.999695 52.573620 0.000000 0.000000 0.000000 +-72.360733 -52.572529 44.721951 -0.471300 -0.583122 0.661699 +-26.286882 -80.901161 52.573765 -0.471300 -0.583122 0.661699 +-42.532269 -30.901140 85.065422 -0.471300 -0.583122 0.661699 +-72.360733 52.572529 44.721951 -0.700224 0.268032 0.661699 +-85.064789 0.000000 52.573593 -0.700224 0.268032 0.661699 +-42.532269 30.901140 85.065422 -0.700224 0.268032 0.661699 +-26.286882 80.901161 52.573765 -0.724505 -0.435120 0.534569 +17.513489 74.639824 106.840172 -0.724505 -0.435120 0.534569 +-25.018951 105.541466 74.348495 -0.724505 -0.435120 0.534569 +52.572979 0.000000 85.065170 -0.637702 0.554588 0.534574 +107.592773 60.428177 88.008667 -0.637702 0.554588 0.534574 +68.818939 49.999695 52.573620 -0.637702 0.554588 0.534574 +52.572979 0.000000 85.065170 0.268034 0.194737 0.943523 +16.245556 49.999527 85.065437 0.268034 0.194737 0.943523 +0.000000 0.000000 100.000000 0.268034 0.194737 0.943523 +52.572979 0.000000 85.065170 0.491119 0.356821 0.794657 +68.818939 49.999695 52.573620 0.491119 0.356821 0.794657 +16.245556 49.999527 85.065437 0.491119 0.356821 0.794657 +68.818939 49.999695 52.573620 0.408946 0.628425 0.661699 +27.638802 85.064926 44.721985 0.408946 0.628425 0.661699 +16.245556 49.999527 85.065437 0.408946 0.628425 0.661699 +16.245556 49.999527 85.065437 -0.102381 0.315090 0.943524 +-42.532269 30.901140 85.065422 -0.102381 0.315090 0.943524 +0.000000 0.000000 100.000000 -0.102381 0.315090 0.943524 +16.245556 49.999527 85.065437 -0.187594 0.577345 0.794658 +-26.286882 80.901161 52.573765 -0.187594 0.577345 0.794658 +-42.532269 30.901140 85.065422 -0.187594 0.577345 0.794658 +-26.286882 80.901161 52.573765 -0.471300 0.583122 0.661699 +-72.360733 52.572529 44.721951 -0.471300 0.583122 0.661699 +-42.532269 30.901140 85.065422 -0.471300 0.583122 0.661699 +-42.532269 30.901140 85.065422 -0.331304 0.000000 0.943524 +-42.532269 -30.901140 85.065422 -0.331304 0.000000 0.943524 +0.000000 0.000000 100.000000 -0.331304 0.000000 0.943524 +-42.532269 -30.901140 85.065422 -0.397323 -0.866029 -0.303527 +-103.155792 0.000000 76.255119 -0.397323 -0.866029 -0.303527 +-85.064789 0.000000 52.573593 -0.397323 -0.866029 -0.303527 +-85.064789 0.000000 52.573593 -0.700224 -0.268032 0.661699 +-72.360733 -52.572529 44.721951 -0.700224 -0.268032 0.661699 +-42.532269 -30.901140 85.065422 -0.700224 -0.268032 0.661699 +16.245556 -49.999527 85.065437 -0.291569 -0.897343 -0.331306 +-43.661819 -34.377480 95.475166 -0.291569 -0.897343 -0.331306 +-42.532269 -30.901140 85.065422 -0.291569 -0.897343 -0.331306 +-42.532269 -30.901140 85.065422 -0.187594 -0.577345 0.794658 +-26.286882 -80.901161 52.573765 -0.187594 -0.577345 0.794658 +16.245556 -49.999527 85.065437 -0.187594 -0.577345 0.794658 +-26.286882 -80.901161 52.573765 0.038530 -0.748779 0.661699 +27.638802 -85.064926 44.721985 0.038530 -0.748779 0.661699 +16.245556 -49.999527 85.065437 0.038530 -0.748779 0.661699 +16.245556 -49.999527 85.065437 0.268034 -0.194737 0.943523 +52.572979 0.000000 85.065170 0.268034 -0.194737 0.943523 +0.000000 0.000000 100.000000 0.268034 -0.194737 0.943523 +16.245556 -49.999527 85.065437 0.491119 -0.356821 0.794657 +68.818939 -49.999695 52.573620 0.491119 -0.356821 0.794657 +52.572979 0.000000 85.065170 0.491119 -0.356821 0.794657 +68.818939 -49.999695 52.573620 0.724042 -0.194736 0.661695 +89.442619 0.000000 44.721561 0.724042 -0.194736 0.661695 +52.572979 0.000000 85.065170 0.724042 -0.194736 0.661695 +95.105782 30.901262 0.000000 0.889697 0.315095 0.330385 +68.818939 49.999695 52.573620 0.889697 0.315095 0.330385 +89.442619 0.000000 44.721561 0.889697 0.315095 0.330385 +95.105782 30.901262 0.000000 0.794656 0.577348 0.187595 +58.778561 80.901672 0.000000 0.794656 0.577348 0.187595 +68.818939 49.999695 52.573620 0.794656 0.577348 0.187595 +58.778561 80.901672 0.000000 0.574602 0.748784 0.330388 +27.638802 85.064926 44.721985 0.574602 0.748784 0.330388 +68.818939 49.999695 52.573620 0.574602 0.748784 0.330388 +0.000000 99.999992 0.000000 -0.024747 0.943521 0.330386 +-26.286882 80.901161 52.573765 -0.024747 0.943521 0.330386 +27.638802 85.064926 44.721985 -0.024747 0.943521 0.330386 +-26.286882 80.901161 52.573765 -0.794660 -0.356825 0.491113 +-69.629707 114.298058 6.706560 -0.794660 -0.356825 0.491113 +-58.778561 80.901672 0.000000 -0.794660 -0.356825 0.491113 +-58.778561 80.901672 0.000000 -0.534576 0.777865 0.330387 +-72.360733 52.572529 44.721951 -0.534576 0.777865 0.330387 +-26.286882 80.901161 52.573765 -0.534576 0.777865 0.330387 +-95.105782 30.901262 0.000000 -0.904989 0.268032 0.330384 +-85.064789 0.000000 52.573593 -0.904989 0.268032 0.330384 +-72.360733 52.572529 44.721951 -0.904989 0.268032 0.330384 +-95.105782 30.901262 0.000000 -0.982246 0.000000 0.187598 +-95.105782 -30.901262 0.000000 -0.982246 0.000000 0.187598 +-85.064789 0.000000 52.573593 -0.982246 0.000000 0.187598 +-95.105782 -30.901262 0.000000 -0.393192 0.823510 -0.408940 +-115.750099 -9.088099 63.775887 -0.393192 0.823510 -0.408940 +-85.064789 0.000000 52.573593 -0.393192 0.823510 -0.408940 +-58.778561 -80.901672 0.000000 -0.534576 -0.777865 0.330387 +-26.286882 -80.901161 52.573765 -0.534576 -0.777865 0.330387 +-72.360733 -52.572529 44.721951 -0.534576 -0.777865 0.330387 +-26.286882 -80.901161 52.573765 0.852624 -0.178409 0.491124 +-8.586899 -126.427719 5.307136 0.852624 -0.178409 0.491124 +0.000000 -99.999992 0.000000 0.852624 -0.178409 0.491124 +0.000000 -99.999992 0.000000 -0.024747 -0.943521 0.330386 +27.638802 -85.064926 44.721985 -0.024747 -0.943521 0.330386 +-26.286882 -80.901161 52.573765 -0.024747 -0.943521 0.330386 +58.778561 -80.901672 0.000000 0.574602 -0.748784 0.330388 +68.818939 -49.999695 52.573620 0.574602 -0.748784 0.330388 +27.638802 -85.064926 44.721985 0.574602 -0.748784 0.330388 +58.778561 -80.901672 0.000000 0.794656 -0.577348 0.187595 +95.105782 -30.901262 0.000000 0.794656 -0.577348 0.187595 +68.818939 -49.999695 52.573620 0.794656 -0.577348 0.187595 +95.105782 -30.901262 0.000000 0.889697 -0.315095 0.330385 +89.442619 0.000000 44.721561 0.889697 -0.315095 0.330385 +68.818939 -49.999695 52.573620 0.889697 -0.315095 0.330385 +58.778561 80.901672 0.000000 0.306569 0.943521 0.125629 +0.000000 99.999992 0.000000 0.306569 0.943521 0.125629 +27.638802 85.064926 44.721985 0.306569 0.943521 0.125629 +58.778561 80.901672 0.000000 0.303531 0.934171 -0.187597 +26.286882 80.901161 -52.573765 0.303531 0.934171 -0.187597 +0.000000 99.999992 0.000000 0.303531 0.934171 -0.187597 +26.286882 80.901161 -52.573765 0.000000 0.000000 0.000000 +0.000000 99.999992 0.000000 0.000000 0.000000 0.000000 +0.000000 99.999992 0.000000 0.000000 0.000000 0.000000 +-58.778561 80.901672 0.000000 -0.802609 0.583126 0.125627 +-95.105782 30.901262 0.000000 -0.802609 0.583126 0.125627 +-72.360733 52.572529 44.721951 -0.802609 0.583126 0.125627 +-68.818939 49.999695 -52.573620 -0.433152 -0.755764 -0.491123 +-151.899200 72.163864 -13.407273 -0.433152 -0.755764 -0.491123 +-125.612350 91.262299 -65.980896 -0.433152 -0.755764 -0.491123 +-68.818939 49.999695 -52.573620 -0.889697 0.315095 -0.330385 +-89.442619 0.000000 -44.721561 -0.889697 0.315095 -0.330385 +-95.105782 30.901262 0.000000 -0.889697 0.315095 -0.330385 +-95.105782 -30.901262 0.000000 -0.802609 -0.583126 0.125627 +-58.778561 -80.901672 0.000000 -0.802609 -0.583126 0.125627 +-72.360733 -52.572529 44.721951 -0.802609 -0.583126 0.125627 +-95.105782 -30.901262 0.000000 -0.794656 -0.577348 -0.187595 +-68.818939 -49.999695 -52.573620 -0.794656 -0.577348 -0.187595 +-58.778561 -80.901672 0.000000 -0.794656 -0.577348 -0.187595 +-68.818939 -49.999695 -52.573620 -0.574602 -0.748784 -0.330388 +-27.638802 -85.064926 -44.721985 -0.574602 -0.748784 -0.330388 +-58.778561 -80.901672 0.000000 -0.574602 -0.748784 -0.330388 +0.000000 -99.999992 0.000000 0.306569 -0.943521 0.125629 +58.778561 -80.901672 0.000000 0.306569 -0.943521 0.125629 +27.638802 -85.064926 44.721985 0.306569 -0.943521 0.125629 +0.000000 -99.999992 0.000000 0.303531 -0.934171 -0.187597 +26.286882 -80.901161 -52.573765 0.303531 -0.934171 -0.187597 +58.778561 -80.901672 0.000000 0.303531 -0.934171 -0.187597 +26.286882 -80.901161 -52.573765 0.534576 -0.777865 -0.330387 +72.360733 -52.572529 -44.721951 0.534576 -0.777865 -0.330387 +58.778561 -80.901672 0.000000 0.534576 -0.777865 -0.330387 +95.105782 -30.901262 0.000000 0.992077 0.000000 0.125628 +95.105782 30.901262 0.000000 0.992077 0.000000 0.125628 +89.442619 0.000000 44.721561 0.992077 0.000000 0.125628 +95.105782 -30.901262 0.000000 0.982246 0.000000 -0.187598 +85.064789 0.000000 -52.573593 0.982246 0.000000 -0.187598 +95.105782 30.901262 0.000000 0.982246 0.000000 -0.187598 +85.064789 0.000000 -52.573593 0.904989 0.268031 -0.330385 +72.360733 52.572529 -44.721951 0.904989 0.268031 -0.330385 +95.105782 30.901262 0.000000 0.904989 0.268031 -0.330385 +42.532269 30.901140 -85.065422 0.471300 0.583122 -0.661699 +26.286882 80.901161 -52.573765 0.471300 0.583122 -0.661699 +72.360733 52.572529 -44.721951 0.471300 0.583122 -0.661699 +42.532269 30.901140 -85.065422 0.187594 0.577345 -0.794658 +-16.245556 49.999527 -85.065445 0.187594 0.577345 -0.794658 +26.286882 80.901161 -52.573765 0.187594 0.577345 -0.794658 +-16.245556 49.999527 -85.065445 -0.038530 0.748779 -0.661699 +-27.638802 85.064926 -44.721985 -0.038530 0.748779 -0.661699 +26.286882 80.901161 -52.573765 -0.038530 0.748779 -0.661699 +-68.818939 49.999695 -52.573620 -0.330382 -0.777870 -0.534571 +-32.940998 75.655319 -112.079636 -0.330382 -0.777870 -0.534571 +-16.245556 49.999527 -85.065445 -0.330382 -0.777870 -0.534571 +-16.245556 49.999527 -85.065445 -0.491119 0.356821 -0.794657 +-52.572979 0.000000 -85.065170 -0.491119 0.356821 -0.794657 +-68.818939 49.999695 -52.573620 -0.491119 0.356821 -0.794657 +-52.572979 0.000000 -85.065170 -0.724042 0.194736 -0.661695 +-89.442619 0.000000 -44.721561 -0.724042 0.194736 -0.661695 +-68.818939 49.999695 -52.573620 -0.724042 0.194736 -0.661695 +-68.818939 -49.999695 -52.573620 0.637702 -0.554588 -0.534574 +-58.824028 -1.681262 -90.777939 0.637702 -0.554588 -0.534574 +-52.572979 0.000000 -85.065170 0.637702 -0.554588 -0.534574 +-52.572979 0.000000 -85.065170 -0.491119 -0.356821 -0.794657 +-16.245556 -49.999527 -85.065445 -0.491119 -0.356821 -0.794657 +-68.818939 -49.999695 -52.573620 -0.491119 -0.356821 -0.794657 +-16.245556 -49.999527 -85.065445 -0.408946 -0.628425 -0.661698 +-27.638802 -85.064926 -44.721985 -0.408946 -0.628425 -0.661698 +-68.818939 -49.999695 -52.573620 -0.408946 -0.628425 -0.661698 +85.064789 0.000000 -52.573593 0.700224 0.268032 -0.661699 +42.532269 30.901140 -85.065422 0.700224 0.268032 -0.661699 +72.360733 52.572529 -44.721951 0.700224 0.268032 -0.661699 +42.532269 30.901140 -85.065422 -0.794656 0.000000 -0.607061 +82.821396 -30.901140 -137.804779 -0.794656 0.000000 -0.607061 +42.532269 -30.901140 -85.065422 -0.794656 0.000000 -0.607061 +42.532269 -30.901140 -85.065422 0.331304 0.000000 -0.943524 +0.000000 0.000000 -100.000000 0.331304 0.000000 -0.943524 +42.532269 30.901140 -85.065422 0.331304 0.000000 -0.943524 +-16.245556 -49.999527 -85.065445 -0.038530 -0.748779 -0.661699 +26.286882 -80.901161 -52.573765 -0.038530 -0.748779 -0.661699 +-27.638802 -85.064926 -44.721985 -0.038530 -0.748779 -0.661699 +26.286882 -80.901161 -52.573765 0.946422 -0.110258 0.303527 +47.859009 -47.294876 -107.629738 0.946422 -0.110258 0.303527 +42.532269 -30.901140 -85.065422 0.946422 -0.110258 0.303527 +42.532269 -30.901140 -85.065422 0.471300 -0.583122 -0.661699 +72.360733 -52.572529 -44.721951 0.471300 -0.583122 -0.661699 +26.286882 -80.901161 -52.573765 0.471300 -0.583122 -0.661699 +68.818939 -49.999695 52.573620 0.330382 0.777870 0.534571 +26.076334 -65.106430 100.972198 0.330382 0.777870 0.534571 +78.649719 -65.106598 68.480392 0.330382 0.777870 0.534571 +27.638802 -85.064926 44.721985 0.000000 0.000000 0.000000 +16.245556 -49.999527 85.065437 0.000000 0.000000 0.000000 +27.638802 -85.064926 44.721985 0.000000 0.000000 0.000000 +37.469582 -100.171829 60.628754 0.408946 -0.628425 0.661699 +78.649719 -65.106598 68.480392 0.408946 -0.628425 0.661699 +26.076334 -65.106430 100.972198 0.408946 -0.628425 0.661699 +27.638802 -85.064926 44.721985 -0.888429 -0.439812 0.131375 +26.076334 -65.106430 100.972198 -0.888429 -0.439812 0.131375 +16.245556 -49.999527 85.065437 -0.888429 -0.439812 0.131375 +68.818939 -49.999695 52.573620 0.514820 -0.439823 -0.735878 +37.469582 -100.171829 60.628754 0.514820 -0.439823 -0.735878 +27.638802 -85.064926 44.721985 0.514820 -0.439823 -0.735878 +128.216461 10.428487 80.156601 0.724042 0.194736 0.661695 +107.592773 60.428177 88.008667 0.724042 0.194736 0.661695 +91.346809 10.428487 120.500206 0.724042 0.194736 0.661695 +89.442619 0.000000 44.721561 0.143749 -0.980856 0.131371 +91.346809 10.428487 120.500206 0.143749 -0.980856 0.131371 +52.572979 0.000000 85.065170 0.143749 -0.980856 0.131371 +68.818939 49.999695 52.573620 0.577378 0.353719 -0.735879 +128.216461 10.428487 80.156601 0.577378 0.353719 -0.735879 +89.442619 0.000000 44.721561 0.577378 0.353719 -0.735879 +119.150688 -86.567238 -52.045685 0.802609 -0.583127 -0.125627 +141.895737 -64.895966 -7.323733 0.802609 -0.583127 -0.125627 +105.568504 -114.896370 -7.323733 0.802609 -0.583127 -0.125627 +72.360733 -52.572529 -44.721951 -0.542274 -0.625533 -0.560934 +105.568504 -114.896370 -7.323733 -0.542274 -0.625533 -0.560934 +58.778561 -80.901672 0.000000 -0.542274 -0.625533 -0.560934 +72.360733 -52.572529 -44.721951 0.427346 0.709039 -0.560928 +141.895737 -64.895966 -7.323733 0.427346 0.709039 -0.560928 +119.150688 -86.567238 -52.045685 0.427346 0.709039 -0.560928 +136.305481 99.030853 -54.730816 0.802609 0.583127 -0.125627 +122.723297 127.359985 -10.008863 0.802609 0.583127 -0.125627 +159.050522 77.359581 -10.008863 0.802609 0.583127 -0.125627 +72.360733 52.572529 -44.721951 0.427346 -0.709039 -0.560927 +159.050522 77.359581 -10.008863 0.427346 -0.709039 -0.560927 +95.105782 30.901262 0.000000 0.427346 -0.709039 -0.560927 +72.360733 52.572529 -44.721951 -0.542274 0.625533 -0.560934 +122.723297 127.359985 -10.008863 -0.542274 0.625533 -0.560934 +136.305481 99.030853 -54.730816 -0.542274 0.625533 -0.560934 +-115.571976 122.164276 -13.407273 -0.794656 0.577348 -0.187595 +-125.612350 91.262299 -65.980896 -0.794656 0.577348 -0.187595 +-151.899200 72.163864 -13.407273 -0.794656 0.577348 -0.187595 +-68.818939 49.999695 -52.573620 0.584919 0.645499 -0.491121 +-115.571976 122.164276 -13.407273 0.584919 0.645499 -0.491121 +-58.778561 80.901672 0.000000 0.584919 0.645499 -0.491121 +-58.778561 80.901672 0.000000 -0.151768 0.110265 0.982247 +-151.899200 72.163864 -13.407273 -0.151768 0.110265 0.982247 +-95.105782 30.901262 0.000000 -0.151768 0.110265 0.982247 +-125.791100 -39.989361 11.202292 -0.904989 -0.268032 0.330385 +-103.046036 -61.660629 55.924244 -0.904989 -0.268032 0.330385 +-115.750099 -9.088099 63.775887 -0.904989 -0.268032 0.330385 +-85.064789 0.000000 52.573593 0.356317 -0.053217 0.932849 +-103.046036 -61.660629 55.924244 0.356317 -0.053217 0.932849 +-72.360733 -52.572529 44.721951 0.356317 -0.053217 0.932849 +-95.105782 -30.901262 0.000000 0.088320 -0.878027 -0.470391 +-103.046036 -61.660629 55.924244 0.088320 -0.878027 -0.470391 +-125.791100 -39.989361 11.202292 0.088320 -0.878027 -0.470391 +-67.365456 -107.329407 5.307136 -0.303531 -0.934171 0.187597 +-8.586899 -126.427719 5.307136 -0.303531 -0.934171 0.187597 +-34.873779 -107.328888 57.880901 -0.303531 -0.934171 0.187597 +-58.778561 -80.901672 0.000000 -0.057971 -0.178416 -0.982246 +-8.586899 -126.427719 5.307136 -0.057971 -0.178416 -0.982246 +-67.365456 -107.329407 5.307136 -0.057971 -0.178416 -0.982246 +-58.778561 -80.901672 0.000000 -0.794660 0.356825 0.491113 +-34.873779 -107.328888 57.880901 -0.794660 0.356825 0.491113 +-26.286882 -80.901161 52.573765 -0.794660 0.356825 0.491113 +15.116003 -53.475864 95.475182 -0.291569 -0.897343 -0.331306 +-46.410179 -42.835899 120.803543 -0.291569 -0.897343 -0.331306 +-42.532269 -30.901140 85.065422 -0.619580 0.762254 0.187325 +-1.129552 -3.476339 110.409752 -0.619580 0.762254 0.187325 +0.000000 0.000000 100.000000 -0.619580 0.762254 0.187325 +0.000000 0.000000 100.000000 0.949294 0.252486 0.187325 +15.116003 -53.475864 95.475182 0.949294 0.252486 0.187325 +16.245556 -49.999527 85.065437 0.949294 0.252486 0.187325 +-10.918813 -66.393265 -107.629761 0.187594 -0.577345 -0.794658 +47.859009 -47.294876 -107.629738 0.187594 -0.577345 -0.794658 +31.613625 -97.294907 -75.138077 0.187594 -0.577345 -0.794658 +42.532269 -30.901140 -85.065422 -0.245566 0.755763 -0.607058 +-10.918813 -66.393265 -107.629761 -0.245566 0.755763 -0.607058 +-16.245556 -49.999527 -85.065445 -0.245566 0.755763 -0.607058 +-16.245556 -49.999527 -85.065445 -0.700857 -0.645501 0.303528 +31.613625 -97.294907 -75.138077 -0.700857 -0.645501 0.303528 +26.286882 -80.901161 -52.573765 -0.700857 -0.645501 0.303528 +4.559107 14.031204 -142.015869 0.102381 0.315090 -0.943524 +-11.686449 64.030731 -127.081314 0.102381 0.315090 -0.943524 +47.091377 44.932346 -127.081284 0.102381 0.315090 -0.943524 +42.532269 30.901140 -85.065422 0.619580 -0.762254 -0.187325 +4.559107 14.031204 -142.015869 0.619580 -0.762254 -0.187325 +47.091377 44.932346 -127.081284 0.619580 -0.762254 -0.187325 +-16.245556 49.999527 -85.065445 -0.949294 -0.252486 -0.187325 +4.559107 14.031204 -142.015869 -0.949294 -0.252486 -0.187325 +0.000000 0.000000 -100.000000 -0.949294 -0.252486 -0.187325 +-75.069984 -51.680954 -58.286392 0.637702 -0.554587 -0.534574 +-93.882179 -11.110394 -122.817253 0.637702 -0.554587 -0.534574 +-58.824028 -1.681262 -90.777939 0.637702 -0.554587 -0.534574 +-89.442619 0.000000 -44.721561 -0.143749 0.980856 -0.131371 +-58.824028 -1.681262 -90.777939 -0.143749 0.980856 -0.131371 +-95.693672 -1.681262 -50.434334 -0.143749 0.980856 -0.131371 +-89.442619 0.000000 -44.721561 -0.577378 -0.353719 0.735879 +-75.069984 -51.680954 -58.286392 -0.577378 -0.353719 0.735879 +-68.818939 -49.999695 -52.573620 -0.577378 -0.353719 0.735879 +-10.851153 133.396393 6.706560 -0.303531 0.934171 0.187597 +-69.629707 114.298058 6.706560 -0.303531 0.934171 0.187597 +-37.138035 114.297554 59.280323 -0.303531 0.934171 0.187597 +0.000000 99.999992 0.000000 -0.057971 0.178416 -0.982246 +-69.629707 114.298058 6.706560 -0.057971 0.178416 -0.982246 +-10.851153 133.396393 6.706560 -0.057971 0.178416 -0.982246 +-26.286882 80.901161 52.573765 0.852624 0.178409 0.491124 +-10.851153 133.396393 6.706560 0.852624 0.178409 0.491124 +-37.138035 114.297554 59.280323 0.852624 0.178409 0.491124 +-60.623276 30.901140 108.746948 -0.607060 0.000000 0.794656 +-103.155792 0.000000 76.255119 -0.607060 0.000000 0.794656 +-60.623276 -30.901140 108.746948 -0.607060 0.000000 0.794656 +-85.064789 0.000000 52.573593 -0.397323 0.866029 -0.303527 +-60.623276 30.901140 108.746948 -0.397323 0.866029 -0.303527 +-42.532269 30.901140 85.065422 -0.397323 0.866029 -0.303527 +-42.532269 30.901140 85.065422 0.794656 0.000000 0.607061 +-60.623276 -30.901140 108.746948 0.794656 0.000000 0.607061 +-42.532269 -30.901140 85.065422 0.794656 0.000000 0.607061 +28.906733 109.705223 66.496719 0.038530 0.748779 0.661699 +-25.018951 105.541466 74.348495 0.038530 0.748779 0.661699 +17.513489 74.639824 106.840172 0.038530 0.748779 0.661699 +27.638802 85.064926 44.721985 0.977271 -0.166382 0.131372 +17.513489 74.639824 106.840172 0.977271 -0.166382 0.131372 +16.245556 49.999527 85.065437 0.977271 -0.166382 0.131372 +-26.286882 80.901161 52.573765 -0.157986 0.658427 -0.735877 +28.906733 109.705223 66.496719 -0.157986 0.658427 -0.735877 +27.638802 85.064926 44.721985 -0.157986 0.658427 -0.735877 +-32.940998 75.655319 -112.079636 -0.408946 0.628425 -0.661698 +-85.514381 75.655487 -79.587814 -0.408946 0.628425 -0.661698 +-44.334244 110.720718 -71.736176 -0.408946 0.628425 -0.661698 +-27.638802 85.064926 -44.721985 0.888429 0.439812 -0.131375 +-32.940998 75.655319 -112.079636 0.888429 0.439812 -0.131375 +-44.334244 110.720718 -71.736176 0.888429 0.439812 -0.131375 +-27.638802 85.064926 -44.721985 -0.514820 0.439823 0.735878 +-85.514381 75.655487 -79.587814 -0.514820 0.439823 0.735878 +-68.818939 49.999695 -52.573620 -0.514820 0.439823 0.735878 +-93.882179 -11.110394 -122.817253 -0.724042 -0.194736 -0.661696 +-110.128143 -61.110085 -90.325714 -0.724042 -0.194736 -0.661696 +-130.751831 -11.110394 -82.473656 -0.724042 -0.194736 -0.661696 +-93.882179 -11.110394 -122.817253 -0.143749 0.980856 -0.131371 +-130.751831 -11.110394 -82.473656 -0.143749 0.980856 -0.131371 +-130.751831 -11.110394 -82.473656 -0.577378 -0.353719 0.735879 +-110.128143 -61.110085 -90.325714 -0.577378 -0.353719 0.735879 +26.286882 80.901161 -52.573765 0.904702 0.119471 0.408950 +1.899393 172.418747 -25.358364 0.904702 0.119471 0.408950 +0.000000 99.999992 0.000000 0.904702 0.119471 0.408950 +-27.638802 85.064926 -44.721985 0.000000 0.000000 0.000000 +-27.638802 85.064926 -44.721985 0.000000 0.000000 0.000000 +26.286882 80.901161 -52.573765 0.000000 0.000000 0.000000 +28.186277 153.319916 -77.932129 0.024747 0.943521 -0.330386 +-25.739407 157.483673 -70.080353 0.024747 0.943521 -0.330386 +1.899393 172.418747 -25.358364 0.024747 0.943521 -0.330386 +-27.638802 85.064926 -44.721985 -0.862349 0.187329 0.470385 +1.899393 172.418747 -25.358364 -0.862349 0.187329 0.470385 +-25.739407 157.483673 -70.080353 -0.862349 0.187329 0.470385 +26.286882 80.901161 -52.573765 -0.160722 -0.322433 -0.932848 +-25.739407 157.483673 -70.080353 -0.160722 -0.322433 -0.932848 +28.186277 153.319916 -77.932129 -0.160722 -0.322433 -0.932848 +125.353912 0.000000 -105.312958 0.607060 0.000000 -0.794656 +82.821396 -30.901140 -137.804779 0.607060 0.000000 -0.794656 +82.821396 30.901140 -137.804779 0.607060 0.000000 -0.794656 +42.532269 -30.901140 -85.065422 0.397323 -0.866029 0.303527 +125.353912 0.000000 -105.312958 0.397323 -0.866029 0.303527 +85.064789 0.000000 -52.573593 0.397323 -0.866029 0.303527 +42.532269 30.901140 -85.065422 0.397323 0.866029 0.303527 +125.353912 0.000000 -105.312958 0.397323 0.866029 0.303527 +82.821396 30.901140 -137.804779 0.397323 0.866029 0.303527 +-46.410179 -42.835899 120.803543 -0.102381 -0.315090 0.943523 +12.367644 -61.934280 120.803574 -0.102381 -0.315090 0.943523 +-3.877912 -11.934759 135.738129 -0.102381 -0.315090 0.943523 +-1.129552 -3.476339 110.409752 -0.619579 0.762254 0.187325 +-46.410179 -42.835899 120.803543 -0.619579 0.762254 0.187325 +-3.877912 -11.934759 135.738129 -0.619579 0.762254 0.187325 +-1.129552 -3.476339 110.409752 0.949295 0.252486 0.187325 +12.367644 -61.934280 120.803574 0.949295 0.252486 0.187325 +15.116003 -53.475864 95.475182 0.949295 0.252486 0.187325 +47.091377 44.932346 -127.081284 0.291569 0.897343 0.331306 +95.105782 -30.901262 0.000000 0.101635 -0.073842 0.992078 +58.778561 -80.901672 0.000000 0.101635 -0.073842 0.992078 +105.568504 -114.896370 -7.323733 0.101635 -0.073842 0.992078 +159.050522 77.359581 -10.008863 0.101635 0.073842 0.992078 +16.245556 49.999527 85.065437 -0.724505 -0.435120 0.534569 +52.572979 0.000000 85.065170 -0.637702 0.554587 0.534574 +91.346809 10.428487 120.500206 -0.637702 0.554587 0.534574 +107.592773 60.428177 88.008667 -0.637702 0.554587 0.534574 +-60.623276 -30.901140 108.746948 -0.397323 -0.866029 -0.303527 +-37.138035 114.297554 59.280323 -0.794660 -0.356825 0.491113 +-95.105782 -30.901262 0.000000 -0.393191 0.823510 -0.408940 +-125.791100 -39.989361 11.202292 -0.393191 0.823510 -0.408940 +-115.750099 -9.088099 63.775887 -0.393191 0.823510 -0.408940 +-34.873779 -107.328888 57.880901 0.852624 -0.178409 0.491124 +-68.818939 49.999695 -52.573620 -0.433152 -0.755764 -0.491122 +-95.105782 30.901262 0.000000 -0.433152 -0.755764 -0.491122 +-151.899200 72.163864 -13.407273 -0.433152 -0.755764 -0.491122 +-85.514381 75.655487 -79.587814 -0.330382 -0.777870 -0.534571 +-75.069984 -51.680954 -58.286392 0.637702 -0.554588 -0.534574 +82.821396 30.901140 -137.804779 -0.794656 0.000000 -0.607061 +31.613625 -97.294907 -75.138077 0.946422 -0.110258 0.303527 +16.245556 -49.999527 85.065437 0.330382 0.777870 0.534571 +37.469582 -100.171829 60.628754 -0.888429 -0.439812 0.131375 +78.649719 -65.106598 68.480392 0.514820 -0.439823 -0.735878 +128.216461 10.428487 80.156601 0.143749 -0.980856 0.131371 +107.592773 60.428177 88.008667 0.577378 0.353719 -0.735879 +119.150688 -86.567238 -52.045685 -0.542274 -0.625533 -0.560934 +72.360733 -52.572529 -44.721951 0.427346 0.709039 -0.560927 +95.105782 -30.901262 0.000000 0.427346 0.709039 -0.560927 +141.895737 -64.895966 -7.323733 0.427346 0.709039 -0.560927 +136.305481 99.030853 -54.730816 0.427346 -0.709039 -0.560927 +58.778561 80.901672 0.000000 -0.542274 0.625533 -0.560934 +-68.818939 49.999695 -52.573620 0.584920 0.645499 -0.491121 +-125.612350 91.262299 -65.980896 0.584920 0.645499 -0.491121 +-115.571976 122.164276 -13.407273 0.584920 0.645499 -0.491121 +-115.571976 122.164276 -13.407273 -0.151768 0.110265 0.982247 +-85.064789 0.000000 52.573593 0.356316 -0.053217 0.932849 +-115.750099 -9.088099 63.775887 0.356316 -0.053217 0.932849 +-103.046036 -61.660629 55.924244 0.356316 -0.053217 0.932849 +-72.360733 -52.572529 44.721951 0.088320 -0.878027 -0.470391 +0.000000 -99.999992 0.000000 -0.057971 -0.178416 -0.982246 +-67.365456 -107.329407 5.307136 -0.794660 0.356825 0.491113 +15.116003 -53.475864 95.475182 -0.291569 -0.897343 -0.331305 +12.367644 -61.934280 120.803574 -0.291569 -0.897343 -0.331305 +-46.410179 -42.835899 120.803543 -0.291569 -0.897343 -0.331305 +-43.661819 -34.377480 95.475166 -0.619580 0.762254 0.187325 +-1.129552 -3.476339 110.409752 0.949294 0.252486 0.187325 +47.859009 -47.294876 -107.629738 -0.245566 0.755763 -0.607058 +-16.245556 -49.999527 -85.065445 -0.700857 -0.645501 0.303527 +-10.918813 -66.393265 -107.629761 -0.700857 -0.645501 0.303527 +31.613625 -97.294907 -75.138077 -0.700857 -0.645501 0.303527 +42.532269 30.901140 -85.065422 0.619579 -0.762254 -0.187325 +0.000000 0.000000 -100.000000 0.619579 -0.762254 -0.187325 +4.559107 14.031204 -142.015869 0.619579 -0.762254 -0.187325 +-11.686449 64.030731 -127.081314 -0.949294 -0.252486 -0.187325 +-110.128143 -61.110085 -90.325714 0.637702 -0.554587 -0.534574 +-52.572979 0.000000 -85.065170 -0.143749 0.980856 -0.131371 +-89.442619 0.000000 -44.721561 -0.577377 -0.353719 0.735879 +-95.693672 -1.681262 -50.434334 -0.577377 -0.353719 0.735879 +-75.069984 -51.680954 -58.286392 -0.577377 -0.353719 0.735879 +-58.778561 80.901672 0.000000 -0.057971 0.178416 -0.982246 +0.000000 99.999992 0.000000 0.852624 0.178409 0.491124 +-103.155792 0.000000 76.255119 -0.397323 0.866029 -0.303527 +-60.623276 30.901140 108.746948 0.794656 -0.000000 0.607061 +28.906733 109.705223 66.496719 0.977271 -0.166382 0.131372 +-26.286882 80.901161 52.573765 -0.157985 0.658427 -0.735877 +-25.018951 105.541466 74.348495 -0.157985 0.658427 -0.735877 +28.906733 109.705223 66.496719 -0.157985 0.658427 -0.735877 +-16.245556 49.999527 -85.065445 0.888429 0.439812 -0.131375 +-44.334244 110.720718 -71.736176 -0.514820 0.439823 0.735878 +-95.693672 -1.681262 -50.434334 -0.577378 -0.353719 0.735879 +28.186277 153.319916 -77.932129 0.904702 0.119471 0.408950 +0.000000 99.999992 0.000000 -0.862349 0.187329 0.470385 +-27.638802 85.064926 -44.721985 -0.160722 -0.322433 -0.932848 +82.821396 -30.901140 -137.804779 0.397323 -0.866029 0.303527 +85.064789 0.000000 -52.573593 0.397323 0.866029 0.303527 +-46.410179 -42.835899 120.803543 -0.619580 0.762254 0.187325 +-3.877912 -11.934759 135.738129 0.949294 0.252486 0.187325 +12.367644 -61.934280 120.803574 0.949294 0.252486 0.187325 +3 0 1 2 +3 3 4 5 +3 6 7 8 +3 9 10 11 +3 12 13 14 +3 15 16 17 +3 18 19 20 +3 21 22 23 +3 24 25 26 +3 27 28 29 +3 30 31 32 +3 33 34 35 +3 36 37 38 +3 39 40 41 +3 42 43 44 +3 45 46 47 +3 48 49 50 +3 51 52 53 +3 54 55 56 +3 57 58 59 +3 60 61 62 +3 63 64 65 +3 66 67 68 +3 69 70 71 +3 72 73 74 +3 75 76 77 +3 78 79 80 +3 81 82 83 +3 84 85 86 +3 87 88 89 +3 90 91 92 +3 93 94 95 +3 96 97 98 +3 99 100 101 +3 102 103 104 +3 105 106 107 +3 108 109 110 +3 111 112 113 +3 114 115 116 +3 117 118 119 +3 120 121 122 +3 123 124 125 +3 126 127 128 +3 129 130 131 +3 132 133 134 +3 135 136 137 +3 138 139 140 +3 141 142 143 +3 144 145 146 +3 147 148 149 +3 150 151 152 +3 153 154 155 +3 156 157 158 +3 159 160 161 +3 162 163 164 +3 165 166 167 +3 168 169 170 +3 171 172 173 +3 174 175 176 +3 177 178 179 +3 180 181 182 +3 183 184 185 +3 186 187 188 +3 189 190 191 +3 192 193 194 +3 195 196 197 +3 198 199 200 +3 201 202 203 +3 204 205 206 +3 207 208 209 +3 210 211 212 +3 213 214 215 +3 216 217 218 +3 219 220 221 +3 222 223 224 +3 225 226 227 +3 228 229 230 +3 231 232 233 +3 234 235 236 +3 237 238 239 +3 240 241 242 +3 243 244 45 +3 47 245 243 +3 246 247 248 +3 249 250 251 +3 252 253 254 +3 255 256 257 +3 258 259 260 +3 261 262 263 +3 264 265 266 +3 267 268 269 +3 270 271 272 +3 273 274 275 +3 276 277 278 +3 279 280 281 +3 282 283 284 +3 285 286 287 +3 288 289 290 +3 291 292 293 +3 294 295 296 +3 297 298 299 +3 300 301 302 +3 303 304 305 +3 306 307 308 +3 309 310 88 +3 311 312 313 +3 314 315 316 +3 317 318 319 +3 320 321 322 +3 323 324 325 +3 326 327 328 +3 329 330 331 +3 332 333 334 +3 335 336 337 +3 338 339 340 +3 341 342 343 +3 344 345 346 +3 347 348 349 +3 350 351 352 +3 353 354 355 +3 356 357 358 +3 359 360 361 +3 362 363 364 +3 365 366 367 +3 368 369 370 +3 371 372 373 +3 374 375 376 +3 377 378 379 +3 380 381 382 +3 340 383 384 +3 342 385 386 +3 387 388 389 +3 158 390 391 +3 391 392 156 +3 393 394 395 +3 396 397 398 +3 399 400 401 +3 402 403 404 +3 405 406 407 +3 408 409 410 +3 411 412 413 +3 414 415 416 +3 417 418 419 +3 12 420 13 +3 421 422 423 +3 42 424 43 +3 45 244 46 +3 54 425 55 +3 426 427 428 +3 81 429 82 +3 87 309 88 +3 117 430 118 +3 431 432 433 +3 135 434 136 +3 156 392 157 +3 435 436 437 +3 204 438 205 +3 213 439 214 +3 225 440 226 +3 234 441 235 +3 240 442 241 +3 243 245 244 +3 47 46 245 +3 249 443 250 +3 252 444 253 +3 258 445 259 +3 261 446 262 +3 267 447 268 +3 448 449 450 +3 276 451 277 +3 279 452 280 +3 453 454 455 +3 288 456 289 +3 457 458 459 +3 297 460 298 +3 303 461 304 +3 306 462 307 +3 463 464 465 +3 311 466 312 +3 314 467 315 +3 320 468 321 +3 469 470 471 +3 472 473 474 +3 332 475 333 +3 335 476 336 +3 338 477 339 +3 478 479 480 +3 347 481 348 +3 350 482 351 +3 356 483 357 +3 359 484 360 +3 365 485 366 +3 486 487 488 +3 374 489 375 +3 377 490 378 +3 340 339 383 +3 342 491 385 +3 387 492 388 +3 158 157 390 +3 391 390 392 +3 396 493 397 +3 399 494 400 +3 405 495 406 +3 408 496 409 +3 312 466 497 +3 467 498 499 diff --git a/apps/devApps/testVkFun/bin/data/lighting.glsl b/apps/devApps/testVkFun/bin/data/lighting.glsl new file mode 100644 index 00000000000..bf80ab0689f --- /dev/null +++ b/apps/devApps/testVkFun/bin/data/lighting.glsl @@ -0,0 +1,11 @@ +#ifndef LIGHTING_GLSL +#define LIGHTING_GLSL + +// Dummy lighting calculations include file +// We use this to demo how shader includes work + +float lambert(in vec3 n, in vec3 l){ + return dot(n,l); +} + +#endif // ifndef LIGHTING_GLSL \ No newline at end of file diff --git a/apps/devApps/testVkFun/bin/data/textured.frag b/apps/devApps/testVkFun/bin/data/textured.frag new file mode 100644 index 00000000000..946cc8121c6 --- /dev/null +++ b/apps/devApps/testVkFun/bin/data/textured.frag @@ -0,0 +1,21 @@ +#version 420 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + + +layout (set = 0, binding = 1) uniform sampler2D tex_0; + +layout (location = 0) in vec2 inTexCoord; + +layout (location = 0) out vec4 outFragColor; + +void main() +{ + + vec4 sampledColor = texture(tex_0, inTexCoord); + outFragColor = sampledColor; + + // outFragColor = vec4(inTexCoord, 0.f, 1.f); + +} \ No newline at end of file diff --git a/apps/devApps/testVkFun/bin/data/textured.vert b/apps/devApps/testVkFun/bin/data/textured.vert new file mode 100644 index 00000000000..d87003898f9 --- /dev/null +++ b/apps/devApps/testVkFun/bin/data/textured.vert @@ -0,0 +1,32 @@ +#version 420 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// uniforms (resources) +layout (set = 0, binding = 0) uniform DefaultMatrices +{ + mat4 projectionMatrix; + mat4 modelMatrix; + mat4 viewMatrix; +}; + +// inputs (vertex attributes) +layout (location = 0) in vec3 inPos; +layout (location = 1) in vec2 inTexCoord; + +// outputs +layout (location = 0) out vec2 outTexCoord; + +// we override the built-in fixed function outputs +// to have more control over the SPIR-V code created. +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main() +{ + outTexCoord = vec2(inTexCoord.x,1.0 -inTexCoord.y); + gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(inPos.xyz, 1.0); +} diff --git a/apps/devApps/testVkFun/src/main.cpp b/apps/devApps/testVkFun/src/main.cpp new file mode 100644 index 00000000000..52e717e9f0a --- /dev/null +++ b/apps/devApps/testVkFun/src/main.cpp @@ -0,0 +1,42 @@ +#include "ofMain.h" +#include "ofApp.h" +#include "vk/ofAppVkNoWindow.h" + + +//======================================================================== +int main(){ + + // Do basic initialisation (mostly setup timers, and randseed) + ofInit(); + + auto consoleLogger = new ofConsoleLoggerChannel(); + ofSetLoggerChannel( std::shared_ptr( consoleLogger, []( ofBaseLoggerChannel * lhs){} ) ); + + // Create a new window + auto mainWindow = std::make_shared(); + // auto mainWindow = std::make_shared(); + + // Store main window in mainloop + ofGetMainLoop()->addWindow( mainWindow ); + + { + ofVkWindowSettings settings; + settings.rendererSettings.setVkVersion( 1, 0, 46 ); + settings.rendererSettings.numSwapchainImages = 3; + settings.rendererSettings.numVirtualFrames = 3; + settings.rendererSettings.presentMode = ::vk::PresentModeKHR::eMailbox; + settings.rendererSettings.requestedQueues = {::vk::QueueFlagBits::eGraphics, ::vk::QueueFlagBits::eCompute}; + +#ifdef NDEBUG + settings.rendererSettings.useDebugLayers = false; +#else + settings.rendererSettings.useDebugLayers = true; +#endif + + // Initialise main window, and associated renderer. + mainWindow->setup( settings ); + } + + // Initialise and start application + ofRunApp( std::make_shared() ); +} diff --git a/apps/devApps/testVkFun/src/ofApp.cpp b/apps/devApps/testVkFun/src/ofApp.cpp new file mode 100644 index 00000000000..86f2e51a422 --- /dev/null +++ b/apps/devApps/testVkFun/src/ofApp.cpp @@ -0,0 +1,445 @@ +#include "ofApp.h" + +#define EXAMPLE_TARGET_FRAME_RATE 60 +bool isFrameLocked = false; + +std::shared_ptr renderer = nullptr; + +//-------------------------------------------------------------- + +void ofApp::setup(){ + + renderer = dynamic_pointer_cast( ofGetCurrentRenderer() ); + + ofDisableSetupScreen(); + ofSetFrameRate( isFrameLocked ? EXAMPLE_TARGET_FRAME_RATE : 0 ); + + setupDrawCommands(); + + setupMeshL(); + + mMeshPly = std::make_shared(); + mMeshPly->load( "ico-m.ply" ); + + setupStaticAllocators(); + uploadStaticData( *renderer->getStagingContext() ); + + mCam.setupPerspective( false, 60, 0.f, 5000 ); + mCam.setPosition( { 0,0, mCam.getImagePlaneDistance() } ); + mCam.lookAt( { 0,0,0 } ); + mCam.setEvents( ofEvents() ); +} + +//-------------------------------------------------------------- + +void ofApp::setupStaticAllocators(){ + + { + + of::vk::BufferAllocator::Settings allocatorSettings; + allocatorSettings + .setRendererProperties( renderer->getVkRendererProperties() ) + .setSize( 1 << 24UL ) // 16 MB + .setFrameCount( 1 ) + .setMemFlags( ::vk::MemoryPropertyFlagBits::eDeviceLocal ) + .setBufferUsageFlags( allocatorSettings.bufferUsageFlags | ::vk::BufferUsageFlagBits::eStorageBuffer ) + ; + mStaticAllocator.setup(allocatorSettings); + } + { + of::vk::ImageAllocator::Settings allocatorSettings; + allocatorSettings + .setRendererProperties( renderer->getVkRendererProperties() ) + .setSize( 1 << 24UL ) // 16 MB + .setMemFlags( ::vk::MemoryPropertyFlagBits::eDeviceLocal ) + ; + mImageAllocator.setup(allocatorSettings); + } +} + +//-------------------------------------------------------------- + +void ofApp::setupDrawCommands(){ + + { + of::vk::Shader::Settings shaderSettings; + shaderSettings + .setDevice(renderer->getVkDevice()) + .setSource( ::vk::ShaderStageFlagBits::eCompute, "compute.glsl" ) + .setPrintDebugInfo(true) + ; + + auto shaderCompute = std::make_shared( shaderSettings ); + + of::vk::ComputePipelineState computePipeline; + computePipeline.setShader( shaderCompute ); + const_cast(computeCmd).setup( computePipeline ); + } + + { + of::vk::Shader::Settings shaderSettings; + shaderSettings + .setDevice(renderer->getVkDevice()) + .setSource(::vk::ShaderStageFlagBits::eVertex , "default.vert") + .setSource(::vk::ShaderStageFlagBits::eFragment, "default.frag") + .setPrintDebugInfo( true ) + ; + + auto mShaderDefault = std::make_shared( shaderSettings ); + + of::vk::GraphicsPipelineState pipeline; + + pipeline.depthStencilState + .setDepthTestEnable( VK_TRUE ) + .setDepthWriteEnable( VK_TRUE ) + ; + + pipeline.inputAssemblyState.setTopology( ::vk::PrimitiveTopology::eTriangleList ); + //pipeline.setPolyMode( ::vk::PolygonMode::eLine ); + pipeline.setShader( mShaderDefault ); + pipeline.blendAttachmentStates[0] + .setBlendEnable( VK_TRUE ) + ; + + const_cast( drawPhong ).setup( pipeline ); + } + + { + of::vk::Shader::Settings shaderSettings; + shaderSettings + .setDevice( renderer->getVkDevice() ) + .setSource( ::vk::ShaderStageFlagBits::eVertex , "fullScreenQuad.vert" ) + .setSource( ::vk::ShaderStageFlagBits::eFragment, "fullScreenQuad.frag" ) + .setPrintDebugInfo( true ) + ; + + auto mShaderFullScreenQuad = std::make_shared( shaderSettings ); + + of::vk::GraphicsPipelineState pipeline; + + pipeline.setShader( mShaderFullScreenQuad ); + pipeline.rasterizationState.setCullMode( ::vk::CullModeFlagBits::eFront ); + pipeline.rasterizationState.setFrontFace( ::vk::FrontFace::eCounterClockwise ); + pipeline.depthStencilState + .setDepthTestEnable( VK_FALSE ) + .setDepthWriteEnable( VK_FALSE ) + ; + pipeline.blendAttachmentStates[0].blendEnable = VK_TRUE; + + const_cast( drawFullScreenQuad ).setup( pipeline ); + const_cast( drawFullScreenQuad ).setNumVertices( 3 ); + } + + { + of::vk::Shader::Settings shaderSettings; + shaderSettings + .setDevice(renderer->getVkDevice()) + .setSource( ::vk::ShaderStageFlagBits::eVertex , "textured.vert" ) + .setSource( ::vk::ShaderStageFlagBits::eFragment, "textured.frag" ) + .setPrintDebugInfo( true ) + ; + + auto mShaderTextured = std::make_shared( shaderSettings ); + + of::vk::GraphicsPipelineState pipeline; + + pipeline.setShader( mShaderTextured ); + pipeline.rasterizationState.setCullMode( ::vk::CullModeFlagBits::eBack ); + pipeline.rasterizationState.setFrontFace( ::vk::FrontFace::eCounterClockwise ); + pipeline.depthStencilState + .setDepthTestEnable( VK_TRUE ) + .setDepthWriteEnable( VK_TRUE ) + ; + pipeline.blendAttachmentStates[0].blendEnable = VK_TRUE; + + const_cast( drawTextured ).setup( pipeline ); + } +} + +//-------------------------------------------------------------- + +void ofApp::update(){ + + ofSetWindowTitle( ofToString( ofGetFrameRate(), 2, ' ' ) ); + + //if ( ofGetFrameNum() % 60 == 0){ + // ofLog() << "Current fps: " << ofGetFrameRate(); + //} + +} + +//-------------------------------------------------------------- + +void ofApp::draw(){ + + auto & currentContext = *renderer->getDefaultContext(); + + + + // In Vulkan, clip space has y flipped, + // and z is mapped from -1..1 to 0..1 (scale 0.5, translate 0.5), + + static const glm::mat4x4 clip ( + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, -1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.5f, 0.0f, + 0.0f, 0.0f, 0.5f, 1.0f + ); + + auto viewMatrix = mCam.getModelViewMatrix(); + auto projectionMatrix = clip * mCam.getProjectionMatrix( ofGetCurrentViewport() ); + + + auto modelMatrix = glm::rotate( float( TWO_PI * ( ( ofGetFrameNum() % 360 ) / 360.f ) ), glm::vec3( { 0.f, 1.f, 0.f } ) ); + + + // Create a fresh copy of our prototype phong draw command + auto hero = drawPhong; + hero + .setUniform( "projectionMatrix", projectionMatrix ) + .setUniform( "viewMatrix", viewMatrix ) + .setUniform( "modelMatrix", modelMatrix ) + .setStorageBuffer( "colorLayout", mStaticColourBuffer ) + .setIndices( mStaticMesh.indexBuffer ) + .setNumIndices( mStaticMesh.indexBuffer.numElements ) + .setDrawMethod(of::vk::DrawCommand::DrawMethod::eIndexed) + .setAttribute( 0, mStaticMesh.posBuffer ) + .setAttribute( 1, mStaticMesh.normalBuffer ) + ; + + auto texturedRect = drawTextured; + texturedRect + .setUniform( "projectionMatrix", projectionMatrix ) + .setUniform( "viewMatrix", viewMatrix ) + .setUniform( "modelMatrix", glm::mat4() ) + .setTexture( "tex_0", mTexture ) + .setIndices( mRectangleData.indexBuffer ) + .setNumIndices(mRectangleData.indexBuffer.numElements) + .setDrawMethod(of::vk::DrawCommand::DrawMethod::eIndexed) + .setAttribute( 0, mRectangleData.posBuffer ) + .setAttribute( 1, mRectangleData.texCoordBuffer ) + ; + + std::vector<::vk::ClearValue> clearValues( 2 ); + clearValues[0].setColor( reinterpret_cast( ofFloatColor::black ) ); + clearValues[1].setDepthStencil( { 1.f, 0 } ); + + of::vk::RenderBatch::Settings settings; + settings.setClearValues(clearValues) + .setContext( renderer->getDefaultContext().get() ) + .setRenderArea( ::vk::Rect2D( {}, { uint32_t( renderer->getViewportWidth() ), uint32_t( renderer->getViewportHeight() ) } ) ) + .setRenderPass( *renderer->getDefaultRenderpass() ) + .setFramebufferAttachmentsExtent( renderer->getSwapchain()->getWidth(), renderer->getSwapchain()->getHeight() ) + .addFramebufferAttachment( renderer->getDefaultContext()->getSwapchainImageView() ) + .addFramebufferAttachment( renderer->getDepthStencilImageView() ) + ; + + of::vk::RenderBatch batch{ settings }; + + batch.begin(); + batch + .draw( drawFullScreenQuad ) + .draw( hero ) + .draw( texturedRect); + ; + batch.end(); + + // submitting the compute command after the batch has been submitted + // means it will end up on the queue *after* the draw instructions. + + /*auto comp = computeCmd; + comp.setStorageBuffer( "ParticleBuf", mParticlesRegion ); + uint32_t flipFlop = ofGetFrameNum() % 2; + comp.setUniform( "flipFlop", flipFlop ); + comp.submit( currentContext, {1,1,1} );*/ + +} + +//-------------------------------------------------------------- + +void ofApp::uploadStaticData( of::vk::Context & stagingContext ){ + + ofMesh meshPlane = ofMesh::plane( 1024 / 2, 768 / 2, 2, 2, OF_PRIMITIVE_TRIANGLES ); + + std::array colourVec{{ + {1,0,0,1}, + {1,1,0,1}, + {0,0,1,1}, + }}; + + struct Particle + { + glm::vec2 pos; + glm::vec2 vel; + glm::vec4 result; + }; + + std::array particleVec{ + { + { + { 1.f, 1.f }, + { 0.5f, 0.5f}, + { 0.f, 0.f, 0.f, 0.f} + }, + { + { 0.f, 0.f}, + { 0.0f, 0.0f }, + { 1.f, 1.f, 1.f, 1.f } + } + } + }; + + std::vector srcDataVec = { + // data for our strange hero object + { + mMeshPly->getIndexPointer(), + mMeshPly->getNumIndices(), + sizeof( ofIndexType ), + }, + { + mMeshPly->getVerticesPointer(), + mMeshPly->getNumVertices(), + sizeof( ofDefaultVertexType ), + }, + { + mMeshPly->getNormalsPointer(), + mMeshPly->getNumNormals(), + sizeof( ofDefaultNormalType ), + }, + // ---- data for textured plane + { + meshPlane.getIndexPointer(), + meshPlane.getNumIndices(), + sizeof( ofIndexType ), + }, + { + meshPlane.getVerticesPointer(), + meshPlane.getNumVertices(), + sizeof(ofDefaultVertexType), + }, + { + meshPlane.getTexCoordsPointer(), + meshPlane.getNumTexCoords(), + sizeof(ofDefaultTexCoordType) + }, + // data for the storage buffer + { + colourVec.data(), + colourVec.size(), + sizeof(glm::vec4) + }, + // data for the particle storage buffer + { + particleVec.data(), + particleVec.size(), + sizeof(Particle), + } + + }; + + const auto & staticBuffer = mStaticAllocator.getBuffer(); + + std::vector bufferRegions = stagingContext.storeBufferDataCmd( srcDataVec, mStaticAllocator ); + + if ( bufferRegions.size() == 8 ) { + mStaticMesh.indexBuffer = bufferRegions[0]; + mStaticMesh.posBuffer = bufferRegions[1]; + mStaticMesh.normalBuffer = bufferRegions[2]; + //---- + mRectangleData.indexBuffer = bufferRegions[3]; + mRectangleData.posBuffer = bufferRegions[4]; + mRectangleData.texCoordBuffer = bufferRegions[5]; + + mStaticColourBuffer = bufferRegions[6]; + mParticlesRegion = bufferRegions[7]; + } + + ofPixels pix; + ofLoadImage( pix, "brighton.png" ); + + of::vk::ImageTransferSrcData imgData; + imgData.pData = pix.getData(); + imgData.numBytes = pix.size(); + imgData.extent.width = pix.getWidth(); + imgData.extent.height = pix.getHeight(); + + mImage = stagingContext.storeImageCmd( imgData, mImageAllocator ); + + of::vk::Texture::Settings textureSettings; + textureSettings + .setDevice(renderer->getVkDevice()) + .setImage(*mImage) + ; + mTexture.setup(textureSettings); + +} + +//-------------------------------------------------------------- + +void ofApp::keyPressed(int key){ + +} + +//-------------------------------------------------------------- + +void ofApp::keyReleased(int key){ + if ( key == ' ' ){ + const_cast( drawPhong ).getPipelineState().touchShader(); + // const_cast( drawFullScreenQuad ).getPipelineState().touchShader(); + // const_cast( drawTextured ).getPipelineState().touchShader(); + } else if ( key == 'l' ){ + isFrameLocked ^= true; + ofSetFrameRate( isFrameLocked ? EXAMPLE_TARGET_FRAME_RATE : 0); + ofLog() << "Framerate " << ( isFrameLocked ? "" : "un" ) << "locked."; + } else if ( key == 'f' ){ + ofToggleFullscreen(); + } +} + +//-------------------------------------------------------------- + +void ofApp::mouseMoved(int x, int y ){ + +} + +//-------------------------------------------------------------- + +void ofApp::mouseDragged(int x, int y, int button){ + +} + +//-------------------------------------------------------------- +void ofApp::mousePressed(int x, int y, int button){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseReleased(int x, int y, int button){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseEntered(int x, int y){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseExited(int x, int y){ + +} + +//-------------------------------------------------------------- +void ofApp::windowResized(int w, int h){ + mCam.setControlArea( {0,0,float(w),float(h)} ); +} + +//-------------------------------------------------------------- +void ofApp::gotMessage(ofMessage msg){ + +} + +//-------------------------------------------------------------- +void ofApp::dragEvent(ofDragInfo dragInfo){ + +} + diff --git a/apps/devApps/testVkFun/src/ofApp.h b/apps/devApps/testVkFun/src/ofApp.h new file mode 100644 index 00000000000..fcf9db52911 --- /dev/null +++ b/apps/devApps/testVkFun/src/ofApp.h @@ -0,0 +1,85 @@ +#pragma once + +#include "ofMain.h" +#include "vk/ofVkRenderer.h" + +struct StaticMesh +{ + of::vk::BufferRegion indexBuffer; + of::vk::BufferRegion posBuffer; + of::vk::BufferRegion normalBuffer; + of::vk::BufferRegion texCoordBuffer; +}; + +class ofApp : public ofBaseApp{ + + of::vk::DrawCommand drawPhong; + of::vk::DrawCommand drawFullScreenQuad; + of::vk::DrawCommand drawTextured; + + of::vk::ComputeCommand computeCmd; + + ofEasyCam mCam; + + std::shared_ptr mMeshL; + std::shared_ptr mMeshPly; + + of::vk::BufferAllocator mStaticAllocator; + of::vk::ImageAllocator mImageAllocator; + + std::shared_ptr<::vk::Image> mImage; + of::vk::Texture mTexture; + + StaticMesh mStaticMesh; + StaticMesh mRectangleData; + + of::vk::BufferRegion mStaticColourBuffer; + of::vk::BufferRegion mParticlesRegion; + + public: + void setup(); + void setupStaticAllocators(); + void setupDrawCommands(); + void setupMeshL(){ + // Horizontally elongated "L___" shape + mMeshL = make_shared(); + vector vert{ + { 0.f,0.f,0.f }, + { 20.f,20.f,0.f }, + { 0.f,100.f,0.f }, + { 20.f,100.f,0.f }, + { 200.f,0.f,0.f }, + { 200.f,20.f,0.f } + }; + + vector idx{ + 0, 1, 2, + 1, 3, 2, + 0, 4, 1, + 1, 4, 5, + }; + + vector norm( vert.size(), { 0, 0, 1.f } ); + + mMeshL->addVertices( vert ); + mMeshL->addNormals( norm ); + mMeshL->addIndices( idx ); + } + void update(); + void draw(); + + void uploadStaticData( of::vk::Context & stagingContext ); + + void keyPressed(int key); + void keyReleased(int key); + void mouseMoved(int x, int y ); + void mouseDragged(int x, int y, int button); + void mousePressed(int x, int y, int button); + void mouseReleased(int x, int y, int button); + void mouseEntered(int x, int y); + void mouseExited(int x, int y); + void windowResized(int w, int h); + void dragEvent(ofDragInfo dragInfo); + void gotMessage(ofMessage msg); + +}; diff --git a/apps/devApps/testVkFun/testVkFun.sln b/apps/devApps/testVkFun/testVkFun.sln new file mode 100644 index 00000000000..673ec64be6c --- /dev/null +++ b/apps/devApps/testVkFun/testVkFun.sln @@ -0,0 +1,35 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testVkFun", "testVkFun.vcxproj", "{7FD42DF7-442E-479A-BA76-D0022F99702A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "openframeworksLib", "..\..\..\libs\openFrameworksCompiled\project\vs\openframeworksLib.vcxproj", "{5837595D-ACA9-485C-8E76-729040CE4B0B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|Win32.ActiveCfg = Debug|Win32 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|Win32.Build.0 = Debug|Win32 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|x64.ActiveCfg = Debug|x64 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|x64.Build.0 = Debug|x64 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|Win32.ActiveCfg = Release|Win32 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|Win32.Build.0 = Release|Win32 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|x64.ActiveCfg = Release|x64 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|x64.Build.0 = Release|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|Win32.ActiveCfg = Debug|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|Win32.Build.0 = Debug|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|x64.ActiveCfg = Debug|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|x64.Build.0 = Debug|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|Win32.ActiveCfg = Release|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|Win32.Build.0 = Release|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|x64.ActiveCfg = Release|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/apps/devApps/testVkFun/testVkFun.vcxproj b/apps/devApps/testVkFun/testVkFun.vcxproj new file mode 100644 index 00000000000..ef48d4b9802 --- /dev/null +++ b/apps/devApps/testVkFun/testVkFun.vcxproj @@ -0,0 +1,198 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {4608CE1A-4EBE-4C93-88B6-B9C43F11D208} + Win32Proj + testVkFun + + + + Application + Unicode + v141 + + + Application + Unicode + v141 + + + Application + Unicode + true + v141 + + + Application + Unicode + true + v141 + + + + + + + + + + + + + + + + + + + + + bin\ + obj\$(Configuration)\ + $(ProjectName)_debug + true + true + + + bin\ + obj\$(Configuration)\ + $(ProjectName)_debug + true + true + + + bin\ + obj\$(Configuration)\ + false + + + bin\ + obj\$(Configuration)\ + false + + + + Disabled + EnableFastChecks + %(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + %(AdditionalIncludeDirectories) + CompileAsCpp + + + true + Console + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + Disabled + EnableFastChecks + %(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + %(AdditionalIncludeDirectories) + CompileAsCpp + true + + + true + Console + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + false + %(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + %(AdditionalIncludeDirectories) + CompileAsCpp + true + + + false + false + Console + true + true + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + false + %(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + %(AdditionalIncludeDirectories) + CompileAsCpp + + + false + false + Console + true + true + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + + + + + + + + {5837595d-aca9-485c-8e76-729040ce4b0b} + + + + + /D_DEBUG %(AdditionalOptions) + /D_DEBUG %(AdditionalOptions) + $(OF_ROOT)\libs\openFrameworksCompiled\project\vs + + + + + + + + + \ No newline at end of file diff --git a/apps/devApps/testVkFun/testVkFun.vcxproj.filters b/apps/devApps/testVkFun/testVkFun.vcxproj.filters new file mode 100644 index 00000000000..c5325387daa --- /dev/null +++ b/apps/devApps/testVkFun/testVkFun.vcxproj.filters @@ -0,0 +1,24 @@ + + + + + src + + + src + + + + + {d8376475-7454-4a24-b08a-aac121d3ad6f} + + + + + src + + + + + + diff --git a/apps/devApps/testVkImGui/addons.make b/apps/devApps/testVkImGui/addons.make new file mode 100644 index 00000000000..cc22bd5c8e2 --- /dev/null +++ b/apps/devApps/testVkImGui/addons.make @@ -0,0 +1 @@ +ofxImGui diff --git a/apps/devApps/testVkImGui/bin/data/.gitkeep b/apps/devApps/testVkImGui/bin/data/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/devApps/testVkImGui/bin/data/default.frag b/apps/devApps/testVkImGui/bin/data/default.frag new file mode 100644 index 00000000000..a6890e195d3 --- /dev/null +++ b/apps/devApps/testVkImGui/bin/data/default.frag @@ -0,0 +1,33 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +layout (location = 0) in vec4 inColor; +layout (location = 1) in vec3 inNormal; + +// uniforms (resources) +layout (set = 0, binding = 0) uniform DefaultMatrices +{ + mat4 projectionMatrix; + mat4 modelMatrix; + mat4 viewMatrix; +}; +// layout (set = 1, binding = 0) uniform StyleSet +// { +// vec4 globalColor; +// } style; + + +layout (location = 0) out vec4 outFragColor; + +void main() +{ + + vec4 normalColor = vec4((inNormal + vec3(1.0)) * vec3(0.5) , 1.0); + vec4 vertexColor = inColor; + normalColor = modelMatrix * vertexColor; + + // set the actual fragment color here + outFragColor = vertexColor; +} \ No newline at end of file diff --git a/apps/devApps/testVkImGui/bin/data/default.vert b/apps/devApps/testVkImGui/bin/data/default.vert new file mode 100644 index 00000000000..78080708353 --- /dev/null +++ b/apps/devApps/testVkImGui/bin/data/default.vert @@ -0,0 +1,40 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// uniforms (resources) +layout (set = 0, binding = 0) uniform DefaultMatrices +{ + mat4 projectionMatrix; + mat4 modelMatrix; + mat4 viewMatrix; +}; // note: if you don't specify a variable name for the block its elements will live in the global namespace. + +layout (set = 0, binding = 1) uniform Style +{ + vec4 globalColor; +} style; + +// inputs (vertex attributes) +layout (location = 0) in vec3 inPos; +layout (location = 1) in vec4 inColor; +layout (location = 2) in vec3 inNormal; + +// outputs +layout (location = 0) out vec4 outColor; +layout (location = 1) out vec3 outNormal; + +// we override the built-in fixed function outputs +// to have more control over the SPIR-V code created. +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main() +{ + outNormal = (inverse(transpose( viewMatrix * modelMatrix)) * vec4(inNormal, 0.0)).xyz; + outColor = style.globalColor; + gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(inPos.xyz, 1.0); +} diff --git a/apps/devApps/testVkImGui/bin/data/fullScreenQuad.frag b/apps/devApps/testVkImGui/bin/data/fullScreenQuad.frag new file mode 100644 index 00000000000..51a59fd471a --- /dev/null +++ b/apps/devApps/testVkImGui/bin/data/fullScreenQuad.frag @@ -0,0 +1,14 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// inputs +layout (location = 0) in vec2 inTexCoord; + +// outputs +layout (location = 0) out vec4 outFragColor; + +void main(){ + outFragColor = vec4(inTexCoord, 1, 0.5); +} \ No newline at end of file diff --git a/apps/devApps/testVkImGui/bin/data/fullScreenQuad.vert b/apps/devApps/testVkImGui/bin/data/fullScreenQuad.vert new file mode 100644 index 00000000000..dbc2e6e10d4 --- /dev/null +++ b/apps/devApps/testVkImGui/bin/data/fullScreenQuad.vert @@ -0,0 +1,24 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// outputs +layout (location = 0) out vec2 outTexCoord; + +// we override the built-in fixed function outputs +// to have more control over the SPIR-V code created. +out gl_PerVertex +{ + vec4 gl_Position; +}; + +// This shader built after a technique introduced in: +// http://www.saschawillems.de/?page_id=2122 + +void main() +{ + outTexCoord = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + gl_Position = vec4(outTexCoord * 2.0f + -1.0f, 0.0f, 1.0f); + +} \ No newline at end of file diff --git a/apps/devApps/testVkImGui/bin/data/imgui.frag b/apps/devApps/testVkImGui/bin/data/imgui.frag new file mode 100644 index 00000000000..75324d05911 --- /dev/null +++ b/apps/devApps/testVkImGui/bin/data/imgui.frag @@ -0,0 +1,18 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +layout (set = 0, binding = 1) uniform sampler2D tex_unit_0; + +// inputs +layout (location = 0) in vec4 inColor; +layout (location = 1) in vec2 inTexCoord; + +// outputs +layout (location = 0) out vec4 outFragColor; + +void main() +{ + outFragColor = inColor * texture( tex_unit_0, inTexCoord.st); +} diff --git a/apps/devApps/testVkImGui/bin/data/imgui.vert b/apps/devApps/testVkImGui/bin/data/imgui.vert new file mode 100644 index 00000000000..57c63a2f934 --- /dev/null +++ b/apps/devApps/testVkImGui/bin/data/imgui.vert @@ -0,0 +1,33 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// uniforms (resources) +layout (set = 0, binding = 0) uniform DefaultMatrices +{ + mat4 modelViewProjectionMatrix; +}; + +// inputs (vertex attributes) +layout (location = 0) in vec2 inPos; +layout (location = 1) in vec2 inTexCoord; +layout (location = 2) in vec4 inColor; + +// outputs +layout (location = 0) out vec4 outColor; +layout (location = 1) out vec2 outTexCoord; + +// we override the built-in fixed function outputs +// to have more control over the SPIR-V code created. +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main() +{ + outTexCoord = inTexCoord; + outColor = inColor; + gl_Position = modelViewProjectionMatrix * vec4(inPos,0,1); +} \ No newline at end of file diff --git a/apps/devApps/testVkImGui/icon.rc b/apps/devApps/testVkImGui/icon.rc new file mode 100644 index 00000000000..7e26eb3534f --- /dev/null +++ b/apps/devApps/testVkImGui/icon.rc @@ -0,0 +1,8 @@ +// Icon Resource Definition +#define MAIN_ICON 102 + +#if defined(_DEBUG) +MAIN_ICON ICON "icon_debug.ico" +#else +MAIN_ICON ICON "icon.ico" +#endif diff --git a/apps/devApps/testVkImGui/src/main.cpp b/apps/devApps/testVkImGui/src/main.cpp new file mode 100644 index 00000000000..4e83b003a57 --- /dev/null +++ b/apps/devApps/testVkImGui/src/main.cpp @@ -0,0 +1,48 @@ +#include "ofMain.h" +#include "ofApp.h" +#include "ofxImGuiLoggerChannel.h" + +int main(){ + + // Basic initialisation (mostly setup timers, and randseed) + ofInit(); + + //auto consoleLogger = new ofConsoleLoggerChannel(); + //ofSetLoggerChannel( std::shared_ptr( consoleLogger, []( ofBaseLoggerChannel * lhs){ } ) ); + + // set the logger to imgui - this will allow imgui to use its own logger. + auto imGuiLogger = new ofxImGui::LoggerChannel(); + ofSetLoggerChannel( std::shared_ptr( imGuiLogger, []( ofBaseLoggerChannel * lhs ){} ) ); + + + // Create a new window + auto mainWindow = std::make_shared(); + + // use this instead to render using the image swapchain + // auto mainWindow = std::make_shared(); + + // Store main window in mainloop + ofGetMainLoop()->addWindow( mainWindow ); + + { + ofVkWindowSettings settings; + settings.rendererSettings.setVkVersion( 1, 0, 46 ); + settings.rendererSettings.numSwapchainImages = 3; + settings.rendererSettings.numVirtualFrames = 3; + settings.rendererSettings.presentMode = ::vk::PresentModeKHR::eMailbox; + + // Only load debug layers if app is compiled in Debug mode +#ifdef NDEBUG + settings.rendererSettings.useDebugLayers = false; +#else + settings.rendererSettings.useDebugLayers = true; +#endif + + // Initialise main window, and associated renderer. + mainWindow->setup( settings ); +} + + // Initialise and start application + ofRunApp( new ofApp() ); + +} diff --git a/apps/devApps/testVkImGui/src/ofApp.cpp b/apps/devApps/testVkImGui/src/ofApp.cpp new file mode 100644 index 00000000000..cdb4b92709c --- /dev/null +++ b/apps/devApps/testVkImGui/src/ofApp.cpp @@ -0,0 +1,268 @@ +#include "ofApp.h" +#include "ofVkRenderer.h" +#include "EngineVk.h" + +// We keep a shared pointer to the renderer so we don't have to +// fetch it anew every time we need it. +std::shared_ptr renderer; + + +//-------------------------------------------------------------- +// Usage: +// static ExampleAppLog my_log; +// my_log.AddLog("Hello %d world\n", 123); +// my_log.Draw("title"); +struct LogPanel +{ + ImGuiTextBuffer* Buf = &ofxImGui::LoggerChannel::getBuffer(); + ImGuiTextFilter Filter; + ImVector LineOffsets; // Index to lines offset + bool ScrollToBottom; + + void Clear(){ + Buf->clear(); LineOffsets.clear(); + } + + void AddLog( const char* fmt, ... ) IM_PRINTFARGS( 2 ){ + int old_size = Buf->size(); + va_list args; + va_start( args, fmt ); + Buf->appendv( fmt, args ); + va_end( args ); + for ( int new_size = Buf->size(); old_size < new_size; old_size++ ) + if ( (*Buf)[old_size] == '\n' ) + LineOffsets.push_back( old_size ); + ScrollToBottom = true; + } + + void Draw( const char* title, bool* p_open = NULL ){ + ImGui::SetNextWindowSize( ImVec2( 500, 400 ), ImGuiSetCond_FirstUseEver ); + ImGui::Begin( title, p_open ); + if ( ImGui::Button( "Clear" ) ) Clear(); + ImGui::SameLine(); + bool copy = ImGui::Button( "Copy" ); + ImGui::SameLine(); + Filter.Draw( "Filter", -100.0f ); + ImGui::Separator(); + ImGui::BeginChild( "scrolling", ImVec2( 0, 0 ), false, ImGuiWindowFlags_HorizontalScrollbar ); + if ( copy ) ImGui::LogToClipboard(); + + if ( Filter.IsActive() ){ + const char* buf_begin = Buf->begin(); + const char* line = buf_begin; + for ( int line_no = 0; line != NULL; line_no++ ){ + const char* line_end = ( line_no < LineOffsets.Size ) ? buf_begin + LineOffsets[line_no] : NULL; + if ( Filter.PassFilter( line, line_end ) ) + ImGui::TextUnformatted( line, line_end ); + line = line_end && line_end[1] ? line_end + 1 : NULL; + } + } else{ + ImGui::TextUnformatted( Buf->begin() ); + } + + if ( ScrollToBottom ) + ImGui::SetScrollHere( 1.0f ); + ScrollToBottom = false; + ImGui::EndChild(); + ImGui::End(); + } +}; + +//-------------------------------------------------------------- +void ofApp::setup(){ + ofDisableSetupScreen(); + + renderer = dynamic_pointer_cast( ofGetCurrentRenderer() ); + + { + // Set up a Draw Command which draws a full screen quad. + // + // This command uses the vertex shader to emit vertices, so + // doesn't need any geometry to render. + + of::vk::Shader::Settings shaderSettings; + + shaderSettings.device = renderer->getVkDevice(); + shaderSettings.printDebugInfo = true; + shaderSettings.setSource(::vk::ShaderStageFlagBits::eVertex ,std::filesystem::path("fullScreenQuad.vert")); + shaderSettings.setSource(::vk::ShaderStageFlagBits::eFragment,std::filesystem::path("fullScreenQuad.frag")); + + mShaderFullscreen = std::make_shared( shaderSettings ); + + of::vk::GraphicsPipelineState pipeline; + + pipeline.setShader( mShaderFullscreen ); + + // Our full screen quad needs to draw just the back face. This is due to + // how we emit the vertices on the vertex shader. Since this differs from + // the default (back culling) behaviour, we have to set this explicitly. + pipeline.rasterizationState + .setCullMode( ::vk::CullModeFlagBits::eFront ) + .setFrontFace( ::vk::FrontFace::eCounterClockwise ); + + // We don't care about depth testing when drawing the full screen quad. + // It shall always cover the full screen. + pipeline.depthStencilState + .setDepthTestEnable( VK_FALSE ) + .setDepthWriteEnable( VK_FALSE ) + ; + pipeline.blendAttachmentStates[0].blendEnable = VK_TRUE; + + fullscreenQuad.setup( pipeline ); + + // As this draw command issues vertices on the vertex shader + // we must tell it how many vertices to render. + fullscreenQuad.setNumVertices( 3 ); + } + + + + mGui.setup(); + +} + +//-------------------------------------------------------------- + +void ofApp::exit(){ + // we set the logger back to console, so that we don't try to write to the wrong channel upon exit. + auto logger = new ofConsoleLoggerChannel(); + ofSetLoggerChannel( std::shared_ptr( logger, []( ofBaseLoggerChannel * lhs ){} ) ); +} + +//-------------------------------------------------------------- +void ofApp::update(){ + +} + +//-------------------------------------------------------------- +void ofApp::draw(){ + + static ofFloatColor backgroundColor = ofFloatColor::fuchsia; + + static LogPanel appLog; + static bool appLogOpen = true; + static bool debugWindowOpen = true; + + auto mainSettings = ofxImGui::Settings(); + mGui.begin(); + + { + ImGui::SetNextWindowSize( ImVec2( 500, 400 ), ImGuiSetCond_FirstUseEver ); + ImGui::Begin("Debug window", &debugWindowOpen ); + static float floatValue = 0.f; + + ImGui::Text( "Hello, Vulkan!" ); + + // This will change the app background color + ImGui::ColorEdit4( "Background Color", &backgroundColor.r, false ); + ImGui::Text( "Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate ); + ImGui::End(); + appLog.Draw("log", &appLogOpen); + } + + // Fetch the default context. This context is automatically + // set up upon app initialisation to draw to the swapchain. + auto & context = renderer->getDefaultContext(); + + // Batch is a light-weight helper object which encapsulates + // a Vulkan Command Buffer. The command buffer is associated + // with the context it has been created from. As long as the + // command buffer lives on the same thread as the context, and + // only uses resources which are either global readonly static, + // or resources which are temporarily allocated though the + // context inside the context's thread, this is considered + // thread-safe. + + // setup the main pass renderbatch + // + std::vector<::vk::ClearValue> clearValues( 2 ); + clearValues[0].setColor( ( ::vk::ClearColorValue& )backgroundColor ); + clearValues[1].setDepthStencil( { 1.f, 0 } ); + + of::vk::RenderBatch::Settings settings; + settings.clearValues = clearValues; + settings.context = context.get(); + settings.framebufferAttachmentsHeight = renderer->getSwapchain()->getHeight(); + settings.framebufferAttachmentsWidth = renderer->getSwapchain()->getWidth(); + settings.renderArea = ::vk::Rect2D( {}, { uint32_t( renderer->getViewportWidth() ), uint32_t( renderer->getViewportHeight() ) } ); + settings.renderPass = *renderer->getDefaultRenderpass(); + settings.framebufferAttachments = { + context->getSwapchainImageView(), + renderer->getDepthStencilImageView() + }; + + of::vk::RenderBatch batch{ settings }; + auto & imguiEngine = *(dynamic_cast( mGui.engine )); + + batch.begin(); + { + batch.draw( fullscreenQuad ); + imguiEngine.setRenderBatch( batch ); + mGui.end(); // renders imgui into current batch + } + batch.end(); + +} + +//-------------------------------------------------------------- +void ofApp::keyPressed(int key){ + +} + +//-------------------------------------------------------------- +void ofApp::keyReleased(int key){ + if ( key == ' ' ){ + // Recompile the full screen shader and + // touch (force implicit re-creation of) any + // associated pipelines. + mShaderFullscreen->compile(); + } + else if ( key == 'f' ){ + ofToggleFullscreen(); + } +} + +//-------------------------------------------------------------- +void ofApp::mouseMoved(int x, int y ){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseDragged(int x, int y, int button){ + +} + +//-------------------------------------------------------------- +void ofApp::mousePressed(int x, int y, int button){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseReleased(int x, int y, int button){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseEntered(int x, int y){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseExited(int x, int y){ + +} + +//-------------------------------------------------------------- +void ofApp::windowResized(int w, int h){ + +} + +//-------------------------------------------------------------- +void ofApp::gotMessage(ofMessage msg){ + +} + +//-------------------------------------------------------------- +void ofApp::dragEvent(ofDragInfo dragInfo){ + +} diff --git a/apps/devApps/testVkImGui/src/ofApp.h b/apps/devApps/testVkImGui/src/ofApp.h new file mode 100644 index 00000000000..c22ba19010d --- /dev/null +++ b/apps/devApps/testVkImGui/src/ofApp.h @@ -0,0 +1,35 @@ +#pragma once + +#include "ofMain.h" +#include "vk/DrawCommand.h" +#include "ofxImGui.h" + +#include "ofxImGuiLoggerChannel.h" + +class ofApp : public ofBaseApp{ + + of::vk::DrawCommand fullscreenQuad; + + std::shared_ptr mShaderFullscreen; + + ofxImGui::Gui mGui; + ofParameter testParameter{ "Value", 0.0, -1.5, 1.5 }; + + public: + void setup(); + void update(); + void draw(); + + void keyPressed(int key); + void keyReleased(int key); + void mouseMoved(int x, int y ); + void mouseDragged(int x, int y, int button); + void mousePressed(int x, int y, int button); + void mouseReleased(int x, int y, int button); + void mouseEntered(int x, int y); + void mouseExited(int x, int y); + void windowResized(int w, int h); + void dragEvent(ofDragInfo dragInfo); + void gotMessage(ofMessage msg); + void exit(); +}; diff --git a/apps/devApps/testVkImGui/testVkImGui.sln b/apps/devApps/testVkImGui/testVkImGui.sln new file mode 100644 index 00000000000..6695768b42b --- /dev/null +++ b/apps/devApps/testVkImGui/testVkImGui.sln @@ -0,0 +1,40 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testVkImGui", "testVkImGui.vcxproj", "{AF9041FE-1D04-4D28-83A4-6FDCAA2BEC3E}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "openframeworksLib", "..\..\..\libs\openFrameworksCompiled\project\vs\openframeworksLib.vcxproj", "{5837595D-ACA9-485C-8E76-729040CE4B0B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AF9041FE-1D04-4D28-83A4-6FDCAA2BEC3E}.Debug|Win32.ActiveCfg = Debug|Win32 + {AF9041FE-1D04-4D28-83A4-6FDCAA2BEC3E}.Debug|Win32.Build.0 = Debug|Win32 + {AF9041FE-1D04-4D28-83A4-6FDCAA2BEC3E}.Debug|x64.ActiveCfg = Debug|x64 + {AF9041FE-1D04-4D28-83A4-6FDCAA2BEC3E}.Debug|x64.Build.0 = Debug|x64 + {AF9041FE-1D04-4D28-83A4-6FDCAA2BEC3E}.Release|Win32.ActiveCfg = Release|Win32 + {AF9041FE-1D04-4D28-83A4-6FDCAA2BEC3E}.Release|Win32.Build.0 = Release|Win32 + {AF9041FE-1D04-4D28-83A4-6FDCAA2BEC3E}.Release|x64.ActiveCfg = Release|x64 + {AF9041FE-1D04-4D28-83A4-6FDCAA2BEC3E}.Release|x64.Build.0 = Release|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|Win32.ActiveCfg = Debug|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|Win32.Build.0 = Debug|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|x64.ActiveCfg = Debug|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|x64.Build.0 = Debug|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|Win32.ActiveCfg = Release|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|Win32.Build.0 = Release|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|x64.ActiveCfg = Release|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D77C6550-0D55-4479-90F6-50433BA01B43} + EndGlobalSection +EndGlobal diff --git a/apps/devApps/testVkImGui/testVkImGui.vcxproj b/apps/devApps/testVkImGui/testVkImGui.vcxproj new file mode 100644 index 00000000000..2562cdac277 --- /dev/null +++ b/apps/devApps/testVkImGui/testVkImGui.vcxproj @@ -0,0 +1,226 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {AF9041FE-1D04-4D28-83A4-6FDCAA2BEC3E} + Win32Proj + testVkImGui + + + + Application + Unicode + v141 + + + Application + Unicode + v141 + + + Application + Unicode + true + v141 + + + Application + Unicode + true + v141 + + + + + + + + + + + + + + + + + + + + + bin\ + obj\$(Configuration)\ + $(ProjectName)_debug + true + true + + + bin\ + obj\$(Configuration)\ + $(ProjectName)_debug + true + true + + + bin\ + obj\$(Configuration)\ + false + + + bin\ + obj\$(Configuration)\ + false + + + + Disabled + EnableFastChecks + %(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + %(AdditionalIncludeDirectories);src;..\..\..\addons\ofxImGui\libs;..\..\..\addons\ofxImGui\libs\imgui;..\..\..\addons\ofxImGui\libs\imgui\src;..\..\..\addons\ofxImGui\src + CompileAsCpp + + + true + Console + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + Disabled + EnableFastChecks + %(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + %(AdditionalIncludeDirectories);src;..\..\..\addons\ofxImGui\libs;..\..\..\addons\ofxImGui\libs\imgui;..\..\..\addons\ofxImGui\libs\imgui\src;..\..\..\addons\ofxImGui\src + CompileAsCpp + true + + + true + Console + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + /debug:fastlink /incremental %(AdditionalOptions) + + + + + + false + %(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + %(AdditionalIncludeDirectories);src;..\..\..\addons\ofxImGui\libs;..\..\..\addons\ofxImGui\libs\imgui;..\..\..\addons\ofxImGui\libs\imgui\src;..\..\..\addons\ofxImGui\src + CompileAsCpp + true + + + false + false + Console + true + true + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + false + %(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + %(AdditionalIncludeDirectories);src;..\..\..\addons\ofxImGui\libs;..\..\..\addons\ofxImGui\libs\imgui;..\..\..\addons\ofxImGui\libs\imgui\src;..\..\..\addons\ofxImGui\src + CompileAsCpp + + + false + false + Console + true + true + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {5837595d-aca9-485c-8e76-729040ce4b0b} + + + + + /D_DEBUG %(AdditionalOptions) + /D_DEBUG %(AdditionalOptions) + $(OF_ROOT)\libs\openFrameworksCompiled\project\vs + + + + + + + + + \ No newline at end of file diff --git a/apps/devApps/testVkImGui/testVkImGui.vcxproj.filters b/apps/devApps/testVkImGui/testVkImGui.vcxproj.filters new file mode 100644 index 00000000000..c055e7f4da8 --- /dev/null +++ b/apps/devApps/testVkImGui/testVkImGui.vcxproj.filters @@ -0,0 +1,132 @@ + + + + + src + + + src + + + src + + + src + + + addons\ofxImGui\src + + + addons\ofxImGui\src + + + addons\ofxImGui\src + + + addons\ofxImGui\src + + + addons\ofxImGui\src + + + addons\ofxImGui\src + + + addons\ofxImGui\libs\imgui\src + + + addons\ofxImGui\libs\imgui\src + + + addons\ofxImGui\libs\imgui\src + + + addons\ofxImGui\src + + + addons\ofxImGui\src + + + + + {d8376475-7454-4a24-b08a-aac121d3ad6f} + + + {71834F65-F3A9-211E-73B8-DC85} + + + {0CD6A942-69FF-3BAA-3AF1-ADD1} + + + {18028A43-13D3-1DD5-471A-BE87} + + + {BE2F957E-7DA7-055C-DC70-3070} + + + {296AD70B-2F2E-1462-9E7C-6030} + + + {5962BF19-1468-EB82-01C0-2E72} + + + + + src + + + src + + + addons\ofxImGui\src + + + addons\ofxImGui\src + + + addons\ofxImGui\src + + + addons\ofxImGui\src + + + addons\ofxImGui\src + + + addons\ofxImGui\src + + + addons\ofxImGui\src + + + addons\ofxImGui\src + + + addons\ofxImGui\src + + + addons\ofxImGui\libs\imgui\src + + + addons\ofxImGui\libs\imgui\src + + + addons\ofxImGui\libs\imgui\src + + + addons\ofxImGui\libs\imgui\src + + + addons\ofxImGui\libs\imgui\src + + + addons\ofxImGui\src + + + addons\ofxImGui\src + + + + + + \ No newline at end of file diff --git a/apps/devApps/testVkImGui/testVkImGui.vcxproj.user b/apps/devApps/testVkImGui/testVkImGui.vcxproj.user new file mode 100644 index 00000000000..09a332bc18f --- /dev/null +++ b/apps/devApps/testVkImGui/testVkImGui.vcxproj.user @@ -0,0 +1,19 @@ + + + + $(ProjectDir)/bin + WindowsLocalDebugger + + + $(ProjectDir)/bin + WindowsLocalDebugger + + + $(ProjectDir)/bin + WindowsLocalDebugger + + + $(ProjectDir)/bin + WindowsLocalDebugger + + \ No newline at end of file diff --git a/apps/devApps/vkDemoFlag/addons.make b/apps/devApps/vkDemoFlag/addons.make new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/devApps/vkDemoFlag/bin/data/.gitkeep b/apps/devApps/vkDemoFlag/bin/data/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/devApps/vkDemoFlag/bin/data/helloworld-amatic.png b/apps/devApps/vkDemoFlag/bin/data/helloworld-amatic.png new file mode 100644 index 00000000000..65e7e91a79d Binary files /dev/null and b/apps/devApps/vkDemoFlag/bin/data/helloworld-amatic.png differ diff --git a/apps/devApps/vkDemoFlag/bin/data/shaders/background.frag b/apps/devApps/vkDemoFlag/bin/data/shaders/background.frag new file mode 100644 index 00000000000..392422a475d --- /dev/null +++ b/apps/devApps/vkDemoFlag/bin/data/shaders/background.frag @@ -0,0 +1,25 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +#include + +// inputs +layout (location = 0) in vec2 inTexCoord; + +// outputs +layout (location = 0) out vec4 outFragColor; + +void main(){ + + float dist = 1.f - inTexCoord.y; + + vec4 c1 = vec4(vec3(253,231,104)*1/255.,0.75); + vec4 c2 = vec4(vec3(63,226,246)*1/255.,.1); + + vec3 outColor = getGradient(c1, c2, dist); + // dist = pow(dist,0.2); + + outFragColor = vec4(outColor, 1.0); +} \ No newline at end of file diff --git a/apps/devApps/vkDemoFlag/bin/data/shaders/background.vert b/apps/devApps/vkDemoFlag/bin/data/shaders/background.vert new file mode 100644 index 00000000000..e842b212d64 --- /dev/null +++ b/apps/devApps/vkDemoFlag/bin/data/shaders/background.vert @@ -0,0 +1,23 @@ +#version 450 core + +// This shader built after a technique introduced in: +// http://www.saschawillems.de/?page_id=2122 + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// outputs +layout (location = 0) out vec2 outTexCoord; + +// we override the built-in fixed function outputs +// to have more control over the SPIR-V code created. +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main() +{ + outTexCoord = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + gl_Position = vec4(outTexCoord * 2.0f + -1.0f, 0.0f, 1.0f); +} diff --git a/apps/devApps/vkDemoFlag/bin/data/shaders/flag.frag b/apps/devApps/vkDemoFlag/bin/data/shaders/flag.frag new file mode 100644 index 00000000000..f4db1cf9b73 --- /dev/null +++ b/apps/devApps/vkDemoFlag/bin/data/shaders/flag.frag @@ -0,0 +1,38 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// uniforms (resources) +layout (set = 0, binding = 0) uniform DefaultMatrices +{ + mat4 projectionMatrix; + mat4 modelMatrix; + mat4 viewMatrix; +}; + +layout (set = 0, binding = 2) uniform sampler2D tex_0; + +// inputs from earlier shader stages +layout (location = 0) in vec4 inColor; +layout (location = 1) in vec2 inTexCoord; +layout (location = 2) in vec3 inNormal; + + +// outputs +layout (location = 0) out vec4 outFragColor; + +void main() +{ + + vec4 normalColor = vec4((inNormal + vec3(1.0)) * vec3(0.5) , 1.0); + vec4 vertexColor = inColor * vec4(inTexCoord,0,1); + + + float texBrightness = 1-texture(tex_0, inTexCoord).r; + + + // set the actual fragment color here + outFragColor = vertexColor; + outFragColor = normalColor + vec4(vec3(texBrightness),0); +} \ No newline at end of file diff --git a/apps/devApps/vkDemoFlag/bin/data/shaders/flag.vert b/apps/devApps/vkDemoFlag/bin/data/shaders/flag.vert new file mode 100644 index 00000000000..1cab79585ad --- /dev/null +++ b/apps/devApps/vkDemoFlag/bin/data/shaders/flag.vert @@ -0,0 +1,72 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +#include "shaders/noise.glsl" + +// uniforms (resources) +layout (set = 0, binding = 0) uniform DefaultMatrices +{ + mat4 projectionMatrix; + mat4 modelMatrix; + mat4 viewMatrix; +}; // note: if you don't specify a variable name for the block its elements will live in the global namespace. + +layout (set = 0, binding = 1) uniform Style +{ + vec4 globalColor; + float timeInterval; // time in 3 sec interval +}; + +// inputs (vertex attributes) +layout ( location = 0) in vec3 inPos; +layout ( location = 1) in vec2 inTexCoord; + +// outputs +layout (location = 0) out vec4 outColor; +layout (location = 1) out vec2 outTexCoord; +layout (location = 2) out vec3 outNormal; + +// we override the built-in fixed function outputs +// to have more control over the SPIR-V code created. +out gl_PerVertex +{ + vec4 gl_Position; +}; + +const float TWO_PI = 2 * 3.1415168f; + +void main() +{ + + outTexCoord = inTexCoord * vec2(1, 0.0025f) + vec2(0, gl_InstanceIndex * 0.0025); + outColor = globalColor; + + int instanceId = gl_InstanceIndex % 30; + + vec3 perInstancePos = inPos.xyz + vec3(0, (gl_InstanceIndex + 1) * -1.0 + 200.f, 0); + + float maxAmplitude = 100.f; + + float dummy = timeInterval; + + float waveProgress = (3. - timeInterval/3.f) * TWO_PI + 200.f * 3.1415168f * (perInstancePos.x + perInstancePos.y ); + + float noiseSample = snoise(vec2(perInstancePos.x/-250, perInstancePos.y/5.f)) * 0.2; + + float noiseAmplitude = 20 + * (step(15, instanceId) * 2 -1) + * smoothstep(1.f-abs(noiseSample), 1., perInstancePos.x/-500); + + maxAmplitude += noiseAmplitude; + + perInstancePos.z = sin(waveProgress) * maxAmplitude * (perInstancePos.x * 0.001); + + float firstDerivativeZ = (cos(waveProgress) * maxAmplitude * (perInstancePos.x * 0.001)); + vec3 calculatedNormal = normalize(vec3(firstDerivativeZ, 0, maxAmplitude + 20 + 1)); + + outNormal = (inverse(transpose( viewMatrix * modelMatrix)) * vec4(calculatedNormal,0.0)).xyz; + + gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(perInstancePos, 1.0); +} diff --git a/apps/devApps/vkDemoFlag/bin/data/shaders/gradient.glsl b/apps/devApps/vkDemoFlag/bin/data/shaders/gradient.glsl new file mode 100644 index 00000000000..75b8f481364 --- /dev/null +++ b/apps/devApps/vkDemoFlag/bin/data/shaders/gradient.glsl @@ -0,0 +1,112 @@ +#ifndef GRADIENT_GLSL +#define GRADIENT_GLSL +// _____ ___ +// / / / / gradient.glsl +// / __/ * / /__ (c) ponies & light, 2014. All rights reserved. +// /__/ /_____/ poniesandlight.co.uk +// +// Created by tgfrerer on 18/03/2014. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + + +/// @brief calculates a gradient mapped colour +/// @detail the calculations are based on colour stops c1, c2, c3, +/// and one normlised input value, emphasises constant +/// execution time. +/// @param c1 [rgbw] first colour stop, with .w being the normalised +/// position of the colour stop on the gradient beam. +/// @param c2 [rgbw] second colour stop, with .w being the normalised +/// position of the colour stop on the gradient beam. +/// @param value the input value for gradient mapping, a normalised +/// float +/// @note values are interpolated close to sinusoidal, using +/// smoothstep, sinusoidal interpolation being what Photoshop +/// uses for its gradients. +/// @author @tgfrerer +vec3 getGradient(vec4 c1, vec4 c2, float value_){ + float blend = smoothstep(c1.w, c2.w, value_); + return mix(c1.rgb, c2.rgb, blend); +} + +// ---------------------------------------------------------------------- + +/// @brief calculates a gradient mapped colour +/// @detail the calculations are based on colour stops c1, c2, c3, +/// and one normlised input value, emphasises constant +/// execution time. +/// @param c1 [rgbw] first colour stop, with .w being the normalised +/// position of the colour stop on the gradient beam. +/// @param c2 [rgbw] second colour stop, with .w being the normalised +/// position of the colour stop on the gradient beam. +/// @param c3 [rgbw] third colour stop, with .w being the normalised +/// position of the colour stop on the gradient beam. +/// @param value the input value for gradient mapping, a normalised +/// float +/// @note values are interpolated close to sinusoidal, using +/// smoothstep, sinusoidal interpolation being what Photoshop +/// uses for its gradients. +/// @author @tgfrerer +vec3 getGradient(vec4 c1, vec4 c2, vec4 c3, float value_){ + + float blend1 = smoothstep(c1.w, c2.w, value_); + float blend2 = smoothstep(c2.w, c3.w, value_); + + vec3 + col = mix(c1.rgb, c2.rgb, blend1); + col = mix(col, c3.rgb, blend2); + + return col; +} + +// ---------------------------------------------------------------------- + +/// @brief calculates a gradient mapped colour +/// @detail the calculations are based on colour stops c1, c2, c3, +/// and one normlised input value, emphasises constant +/// execution time. +/// @param c1 [rgbw] first colour stop, with .w being the normalised +/// position of the colour stop on the gradient beam. +/// @param c2 [rgbw] second colour stop, with .w being the normalised +/// position of the colour stop on the gradient beam. +/// @param c3 [rgbw] third colour stop, with .w being the normalised +/// position of the colour stop on the gradient beam. +/// @param c4 [rgbw] fourth colour stop, with .w being the normalised +/// position of the colour stop on the gradient beam. +/// @param value the input value for gradient mapping, a normalised +/// float +/// @note values are interpolated close to sinusoidal, using +/// smoothstep, sinusoidal interpolation being what Photoshop +/// uses for its gradients. +/// @author @tgfrerer +vec3 getGradient(vec4 c1, vec4 c2, vec4 c3, vec4 c4, float value_){ + + float blend1 = smoothstep(c1.w, c2.w, value_); + float blend2 = smoothstep(c2.w, c3.w, value_); + float blend3 = smoothstep(c3.w, c4.w, value_); + + vec3 + col = mix(c1.rgb, c2.rgb, blend1); + col = mix(col, c3.rgb, blend2); + col = mix(col, c4.rgb, blend3); + + return col; +} + +#endif // #ifndef GRADIENT_GLSL \ No newline at end of file diff --git a/apps/devApps/vkDemoFlag/bin/data/shaders/lambert.frag b/apps/devApps/vkDemoFlag/bin/data/shaders/lambert.frag new file mode 100644 index 00000000000..8f816507f7b --- /dev/null +++ b/apps/devApps/vkDemoFlag/bin/data/shaders/lambert.frag @@ -0,0 +1,39 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// uniforms (resources) +layout (set = 0, binding = 0) uniform DefaultMatrices +{ + mat4 projectionMatrix; + mat4 modelMatrix; + mat4 viewMatrix; +}; + +// inputs from earlier shader stages +layout (location = 0) in vec4 inColor; +layout (location = 1) in vec4 inPos; +layout (location = 2) in vec3 inNormal; + +// outputs +layout (location = 0) out vec4 outFragColor; + +void main() +{ + + vec4 lightPos = vec4(1000,1000,1000,1); // world space + lightPos = viewMatrix * lightPos; // light in view space + + vec3 l = normalize(lightPos.xyz - inPos.xyz); // vector from fragment, to light + vec3 n = normalize(inNormal); + + float lambert = dot(l,n); + + vec4 normalColor = vec4((n + vec3(1.0)) * vec3(0.5) , 1.0); + vec4 albedo = vec4(inColor.rgb * lambert, 1); + + // set the actual fragment color here + outFragColor = albedo; + outFragColor = normalColor; +} \ No newline at end of file diff --git a/apps/devApps/vkDemoFlag/bin/data/shaders/lambert.vert b/apps/devApps/vkDemoFlag/bin/data/shaders/lambert.vert new file mode 100644 index 00000000000..67e517c27cb --- /dev/null +++ b/apps/devApps/vkDemoFlag/bin/data/shaders/lambert.vert @@ -0,0 +1,44 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// uniforms (resources) +layout (set = 0, binding = 0) uniform DefaultMatrices +{ + mat4 projectionMatrix; + mat4 modelMatrix; + mat4 viewMatrix; +}; // note: if you don't specify a variable name for the block its elements will live in the global namespace. + +layout (set = 0, binding = 1) uniform Style +{ + vec4 globalColor; +}; + +// inputs (vertex attributes) +layout ( location = 0) in vec3 inPos; +layout ( location = 1) in vec3 inNormal; + +// outputs +layout (location = 0) out vec4 outColor; +layout (location = 1) out vec4 outPos; +layout (location = 2) out vec3 outNormal; + +// we override the built-in fixed function outputs +// to have more control over the SPIR-V code created. +out gl_PerVertex +{ + vec4 gl_Position; +}; + +const float TWO_PI = 2 * 3.1415168f; + +void main() +{ + + outColor = globalColor; + outNormal = (inverse(transpose( viewMatrix * modelMatrix)) * vec4(inNormal,0.0)).xyz; + outPos = viewMatrix * modelMatrix * vec4(inPos, 1.0); + gl_Position = projectionMatrix * outPos; +} diff --git a/apps/devApps/vkDemoFlag/bin/data/shaders/noise.glsl b/apps/devApps/vkDemoFlag/bin/data/shaders/noise.glsl new file mode 100644 index 00000000000..4b8d63d181b --- /dev/null +++ b/apps/devApps/vkDemoFlag/bin/data/shaders/noise.glsl @@ -0,0 +1,77 @@ +#ifndef NOISE_GLSL +#define NOISE_GLSL +// ---------------------------------------------------------------------- +// +// Description : Array and textureless GLSL 2D simplex noise function. +// Author : Ian McEwan, Ashima Arts. +// Maintainer : ijm +// Lastmod : 20110822 (ijm) +// License : Copyright (C) 2011 Ashima Arts. All rights reserved. +// Distributed under the MIT License. See LICENSE file. +// https://github.com/ashima/webgl-noise +// + +vec3 mod289(vec3 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +vec2 mod289(vec2 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +vec3 permute(vec3 x) { + return mod289(((x*34.0)+1.0)*x); +} + +float snoise(vec2 v) +{ + const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0 + 0.366025403784439, // 0.5*(sqrt(3.0)-1.0) + -0.577350269189626, // -1.0 + 2.0 * C.x + 0.024390243902439); // 1.0 / 41.0 + // First corner + vec2 i = floor(v + dot(v, C.yy) ); + vec2 x0 = v - i + dot(i, C.xx); + + // Other corners + vec2 i1; + //i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0 + //i1.y = 1.0 - i1.x; + i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); + // x0 = x0 - 0.0 + 0.0 * C.xx ; + // x1 = x0 - i1 + 1.0 * C.xx ; + // x2 = x0 - 1.0 + 2.0 * C.xx ; + vec4 x12 = x0.xyxy + C.xxzz; + x12.xy -= i1; + + // Permutations + i = mod289(i); // Avoid truncation effects in permutation + vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 )) + + i.x + vec3(0.0, i1.x, 1.0 )); + + vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0); + m = m*m ; + m = m*m ; + + // Gradients: 41 points uniformly over a line, mapped onto a diamond. + // The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287) + + vec3 x = 2.0 * fract(p * C.www) - 1.0; + vec3 h = abs(x) - 0.5; + vec3 ox = floor(x + 0.5); + vec3 a0 = x - ox; + + // Normalise gradients implicitly by scaling m + // Approximation of: m *= inversesqrt( a0*a0 + h*h ); + m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h ); + + // Compute final noise value at P + vec3 g; + g.x = a0.x * x0.x + h.x * x0.y; + g.yz = a0.yz * x12.xz + h.yz * x12.yw; + return 130.0 * dot(m, g); +} + +// ------- + +#endif //#define NOISE_GLSL \ No newline at end of file diff --git a/apps/devApps/vkDemoFlag/icon.rc b/apps/devApps/vkDemoFlag/icon.rc new file mode 100644 index 00000000000..7e26eb3534f --- /dev/null +++ b/apps/devApps/vkDemoFlag/icon.rc @@ -0,0 +1,8 @@ +// Icon Resource Definition +#define MAIN_ICON 102 + +#if defined(_DEBUG) +MAIN_ICON ICON "icon_debug.ico" +#else +MAIN_ICON ICON "icon.ico" +#endif diff --git a/apps/devApps/vkDemoFlag/src/main.cpp b/apps/devApps/vkDemoFlag/src/main.cpp new file mode 100644 index 00000000000..0a744d09eb8 --- /dev/null +++ b/apps/devApps/vkDemoFlag/src/main.cpp @@ -0,0 +1,45 @@ +#include "ofMain.h" +#include "ofApp.h" +#include "vk/ofAppVkNoWindow.h" + +int main(){ + + // Basic initialisation (mostly setup timers, and randseed) + ofInit(); + + auto consoleLogger = new ofConsoleLoggerChannel(); + ofSetLoggerChannel( std::shared_ptr( consoleLogger, []( ofBaseLoggerChannel * lhs){} ) ); + + // Create a new window + auto mainWindow = std::make_shared(); + + // Use this instead to render using the image swapchain + // auto mainWindow = std::make_shared(); + + // Store main window in mainloop + ofGetMainLoop()->addWindow( mainWindow ); + + { + ofVkWindowSettings settings; + settings.rendererSettings.setVkVersion( 1, 0, 46 ); + settings.rendererSettings.numSwapchainImages = 3; + settings.rendererSettings.numVirtualFrames = 3; + settings.rendererSettings.presentMode = ::vk::PresentModeKHR::eMailbox; + settings.width = 1024; + settings.height = 1024; + // Only load debug layers if app is compiled in Debug mode +#ifdef NDEBUG + settings.rendererSettings.useDebugLayers = false; +#else + settings.rendererSettings.useDebugLayers = true; +#endif + + // Initialise main window, and associated renderer. + mainWindow->setup( settings ); + } + + // Initialise and start application + ofRunApp( new ofApp() ); + + +} diff --git a/apps/devApps/vkDemoFlag/src/ofApp.cpp b/apps/devApps/vkDemoFlag/src/ofApp.cpp new file mode 100644 index 00000000000..ffc617a4d47 --- /dev/null +++ b/apps/devApps/vkDemoFlag/src/ofApp.cpp @@ -0,0 +1,421 @@ +#include "ofApp.h" +#include "ofVkRenderer.h" +#include "vk/ofAppVkNoWindow.h" + +// We keep a pointer to the renderer so we don't have to +// fetch it anew every time we need it. +ofVkRenderer* renderer; + +//-------------------------------------------------------------- +void ofApp::setup(){ + + ofDisableSetupScreen(); + + ofSetFrameRate( 0 ); + + renderer = dynamic_cast( ofGetCurrentRenderer().get() ); + + setupDrawCommands(); + setupTextureData(); + setupStaticGeometry(); + + flagStripDraw + .setAttribute( 0, flagVertices ) + .setAttribute( 1, flagTexCoords ) + .setIndices( flagIndices ) + .setDrawMethod( of::vk::DrawCommand::DrawMethod::eIndexed ) + .setNumIndices( flagIndices.numElements ) + .setInstanceCount( 400 ) + .setTexture( "tex_0", mFlagTexture ) + ; + + backGroundDraw + .setNumVertices( 3 ) + ; + + mCam.setupPerspective( false, 60, 0.f, 5000 ); + mCam.setPosition( { 0, 0, mCam.getImagePlaneDistance() } ); + mCam.lookAt( { 0, 0, 0 } ); + + if ( auto ptr = std::dynamic_pointer_cast( ofGetCurrentWindow() ) ){ + ofLog() << "Running in headless mode"; + // When we're in headless mode, we want to make sure to create animation + // at the right tempo - here we set the frame rate to 12, which is what + // we need to create an animated gif. + ofSetTimeModeFixedRate( ofGetFixedStepForFps( 12 ) ); + } else{ + ofLog() << "Running in regular mode"; + mCam.setEvents( ofEvents() ); + } + + mCam.setGlobalOrientation( { 0.923094f, 0.359343f, -0.125523f, -0.0548912f } ); + mCam.setGlobalPosition( { -389.696f, -509.342f, 422.886f } ); +} + +//-------------------------------------------------------------- + +void ofApp::setupDrawCommands(){ + + of::vk::Shader::Settings shaderSettings; + shaderSettings.device = renderer->getVkDevice(); + shaderSettings.printDebugInfo = true; + + { + + shaderSettings.setSource( ::vk::ShaderStageFlagBits::eVertex , "shaders/flag.vert" ); + shaderSettings.setSource( ::vk::ShaderStageFlagBits::eFragment, "shaders/flag.frag" ); + + // Shader which will draw animated and textured flag + mFlagShader = std::make_shared( shaderSettings ); + + // Define pipeline state to use with draw command + of::vk::GraphicsPipelineState pipeline; + + pipeline.setShader( mFlagShader ); + + pipeline.rasterizationState + .setPolygonMode( ::vk::PolygonMode::eFill ) + .setCullMode( ::vk::CullModeFlagBits::eNone ) + .setFrontFace( ::vk::FrontFace::eCounterClockwise ) + ; + pipeline.inputAssemblyState + .setTopology( ::vk::PrimitiveTopology::eTriangleStrip ) + ; + pipeline.depthStencilState + .setDepthTestEnable( VK_TRUE ) + .setDepthWriteEnable( VK_TRUE ) + ; + pipeline.blendAttachmentStates[0].blendEnable = VK_TRUE; + + // Setup draw command using pipeline state above + flagStripDraw.setup( pipeline ); + } + + { + shaderSettings.clearSources(); + shaderSettings.setSource( ::vk::ShaderStageFlagBits::eVertex , "shaders/background.vert" ); + shaderSettings.setSource( ::vk::ShaderStageFlagBits::eFragment, "shaders/background.frag" ); + + // Shader which draws a full screen quad without need for geometry input + mBgShader = std::make_shared( shaderSettings ); + + // Set up a Draw Command which draws a full screen quad. + // + // This command uses the vertex shader to emit vertices, so + // doesn't need any geometry to render. + + of::vk::GraphicsPipelineState pipeline; + pipeline.setShader( mBgShader ); + + // Our full screen quad needs to draw just the back face. This is due to + // how we emit the vertices on the vertex shader. Since this differs from + // the default (back culling) behaviour, we have to set this explicitly. + pipeline.rasterizationState + .setCullMode( ::vk::CullModeFlagBits::eFront ) + .setFrontFace( ::vk::FrontFace::eCounterClockwise ); + + // We don't care about depth testing when drawing the full screen quad. + // It shall always cover the full screen. + pipeline.depthStencilState + .setDepthTestEnable( VK_FALSE ) + .setDepthWriteEnable( VK_FALSE ) + ; + + pipeline.blendAttachmentStates[0].blendEnable = VK_TRUE; + + backGroundDraw.setup( pipeline ); + } + + { + shaderSettings.clearSources(); + + shaderSettings.setSource( ::vk::ShaderStageFlagBits::eVertex , "shaders/lambert.vert" ); + shaderSettings.setSource( ::vk::ShaderStageFlagBits::eFragment, "shaders/lambert.frag" ); + + // Shader which draws using global color, and "lambert" shading + mLambertShader = std::make_shared( shaderSettings ); + + of::vk::GraphicsPipelineState pipeline; + pipeline.setShader( mLambertShader ); + + pipeline.rasterizationState + .setPolygonMode( ::vk::PolygonMode::eFill ) + .setCullMode( ::vk::CullModeFlagBits::eBack ) + .setFrontFace( ::vk::FrontFace::eClockwise ) + ; + pipeline.inputAssemblyState + .setTopology( ::vk::PrimitiveTopology::eTriangleStrip ) + ; + pipeline.depthStencilState + .setDepthTestEnable( VK_TRUE ) + .setDepthWriteEnable( VK_TRUE ) + ; + pipeline.blendAttachmentStates[0].blendEnable = VK_TRUE; + + lambertDraw.setup( pipeline ); + } + +} + +//-------------------------------------------------------------- + +void ofApp::setupTextureData(){ + { + of::vk::ImageAllocator::Settings allocatorSettings; + allocatorSettings + .setRendererProperties( renderer->getVkRendererProperties() ) + .setSize( 1 << 24UL ) // 16 MB + .setMemFlags( ::vk::MemoryPropertyFlagBits::eDeviceLocal ) + ; + + mImageAllocator.setup(allocatorSettings); + } + + // Grab staging context to place pixel data there for upload to device local image memory + auto & stagingContext = renderer->getStagingContext(); + + ofPixels tmpPix; + + ofLoadImage( tmpPix, "helloworld-amatic.png" ); + + // We must make sure our image has an alpha channel when we upload. + tmpPix.setImageType( ofImageType::OF_IMAGE_COLOR_ALPHA ); + + of::vk::ImageTransferSrcData imgTransferData; + imgTransferData.pData = tmpPix.getData(); + imgTransferData.numBytes = tmpPix.size(); + imgTransferData.extent.width = tmpPix.getWidth(); + imgTransferData.extent.height = tmpPix.getHeight(); + + // Queue pixel data for upload to device local memory via image allocator - + // + // This copies the image data immediately into the staging context's device and host visible memory area, + // and queues up a transfer command in the staging context. + // + // Which means that, since the staging context's memory is coherent, the transfer + // to staging memory has completed by the time the staging context begins executing + // its commands. + // + // The transfer command queued up earlier is then executed, and this command + // does the heavy lifting of transferring the image from staging memory to + // device-only target memory ownded by mImageAllocator + mFlagImage = stagingContext->storeImageCmd( imgTransferData, mImageAllocator ); + + of::vk::Texture::Settings textureSettings; + textureSettings + .setDevice(renderer->getVkDevice()) + .setImage(*mFlagImage) + ; + + // Create a Texture (which is a combination of ImageView+Sampler) using vk image + mFlagTexture.setup(textureSettings); + +} + +//-------------------------------------------------------------- + +void ofApp::setupStaticGeometry(){ + + of::vk::BufferAllocator::Settings allocatorSettings; + allocatorSettings.device = renderer->getVkDevice(); + allocatorSettings.frameCount = 1; + allocatorSettings.memFlags = ::vk::MemoryPropertyFlagBits::eDeviceLocal; + allocatorSettings.physicalDeviceProperties = renderer->getVkPhysicalDeviceProperties(); + allocatorSettings.physicalDeviceMemoryProperties = renderer->getVkPhysicalDeviceMemoryProperties(); + allocatorSettings.queueFamilyIndices = { renderer->getVkRendererProperties().graphicsFamilyIndex }; + allocatorSettings.size = 1'000'000; // ~ 1 MB + + mStaticAllocator.setup(allocatorSettings); + + std::vector vertices; + std::vector texCoords; + std::vector indices; + + // Create geometry for single flag strip - which will get instanced when drawn + size_t numElements = 1000; + + for ( size_t i = 0; i != numElements; ++i ){ + vertices.emplace_back( -float( numElements / 2 ) + float( i/2 ), float( i % 2 ), 0.f ); + } + + for ( size_t i = 0; i != numElements; ++i ){ + texCoords.emplace_back( float( i / 2 ) / float( numElements / 2), 1.f - float( i % 2 ) ); + } + + for ( size_t i = 0; i != numElements; ++i ){ + indices.emplace_back( ofIndexType( i ) ); + } + + std::vector transferSrc { + { vertices.data() , vertices.size() , sizeof( glm::vec3 ) }, + { texCoords.data(), texCoords.size(), sizeof( glm::vec2 ) }, + { indices.data() , indices.size() , sizeof( ofIndexType) }, + }; + + // Create geometry for flagpole + auto tmpMesh = ofCylinderPrimitive( 10, 800, 12, 2 ).getMesh(); + + // Add created geometry to transfer jobs list + transferSrc.push_back( { tmpMesh.getVerticesPointer(), tmpMesh.getNumVertices(), sizeof( ofDefaultVertexType ) } ); + transferSrc.push_back( { tmpMesh.getNormalsPointer(), tmpMesh.getNumVertices(), sizeof( ofDefaultNormalType ) } ); + transferSrc.push_back( { tmpMesh.getIndexPointer(), tmpMesh.getNumIndices(), sizeof( ofIndexType ) } ); + + // Upload geometry to device local memory via staging context + auto & stagingContext = renderer->getStagingContext(); + + auto buffersVec = stagingContext->storeBufferDataCmd( transferSrc, mStaticAllocator ); + + // Recieve buffers from storeBuffer operation, and keep them so we can attach them to draw commands + + flagVertices = buffersVec[0]; + flagTexCoords = buffersVec[1]; + flagIndices = buffersVec[2]; + + flagPoleVertices = buffersVec[3]; + flagPoleNormals = buffersVec[4]; + flagPoleIndices = buffersVec[5]; + +} + + + +//-------------------------------------------------------------- + +void ofApp::update(){ + +} + +//-------------------------------------------------------------- +void ofApp::draw(){ + + // Vulkan uses a slightly different clip space than OpenGL - + // In Vulkan, z goes from 0..1, instead of OpenGL's -1..1 + // and y is flipped. + // We apply the clip matrix to the projectionMatrix to transform + // from openFrameworks (GL-style) to Vulkan (Vulkan-style) clip space. + static const glm::mat4x4 clip( + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, -1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.5f, 0.0f, + 0.0f, 0.0f, 0.5f, 1.0f + ); + + auto viewMatrix = mCam.getModelViewMatrix(); + auto projectionMatrix = clip * mCam.getProjectionMatrix( ofGetCurrentViewport() ); + auto modelMatrix = glm::scale( glm::mat4x4(), { 1, 1, 1 } ); + + auto & context = renderer->getDefaultContext(); + + flagStripDraw + .setUniform( "projectionMatrix", projectionMatrix ) + .setUniform( "viewMatrix", viewMatrix ) + .setUniform( "modelMatrix", modelMatrix ) + .setUniform( "globalColor", ofFloatColor::white ) + .setUniform( "timeInterval", fmodf( ofGetElapsedTimef(), 3.f ) ) + ; + + lambertDraw + .setUniform( "projectionMatrix", projectionMatrix ) + .setUniform( "viewMatrix", viewMatrix ) + .setUniform( "modelMatrix", glm::translate( modelMatrix, {0,-200,0}) ) + .setUniform( "globalColor", ofFloatColor::white ) + .setAttribute(0,flagPoleVertices) + .setAttribute(1,flagPoleNormals) + .setIndices(flagPoleIndices) + .setDrawMethod(of::vk::DrawCommand::DrawMethod::eIndexed ) + .setNumIndices(flagPoleIndices.numElements) + ; + + // Setup the main pass RenderBatch + of::vk::RenderBatch::Settings settings; + settings + .setContext( context.get() ) + .setFramebufferAttachmentsExtent( renderer->getSwapchain()->getWidth(), renderer->getSwapchain()->getHeight() ) + .setRenderAreaExtent( uint32_t( renderer->getViewportWidth() ), uint32_t( renderer->getViewportHeight() ) ) + .setRenderPass( *renderer->getDefaultRenderpass() ) + .addFramebufferAttachment( context->getSwapchainImageView() ) + .addClearColorValue( ofFloatColor::white ) + .addFramebufferAttachment( renderer->getDepthStencilImageView() ) + .addClearDepthStencilValue({1.f,0}) + ; + + of::vk::RenderBatch batch{ settings }; + { + // Beginning a batch allocates a new command buffer in its context + // and begins a RenderPass + batch.begin(); + batch.draw( backGroundDraw ); + batch.draw( lambertDraw ); + batch.draw( flagStripDraw ); + // Ending a batch accumulates all draw commands into a command buffer + // and finalizes the command buffer. + batch.end(); + } + +} + +//-------------------------------------------------------------- +void ofApp::keyPressed(int key){ + +} + +//-------------------------------------------------------------- +void ofApp::keyReleased(int key){ + if ( key == ' ' ){ + // Recompile all shaders on spacebar press + if ( mFlagShader ){ + mFlagShader->compile(); + } + if ( mBgShader ){ + mBgShader->compile(); + } + if ( mLambertShader ){ + mLambertShader->compile(); + } + } +} + +//-------------------------------------------------------------- +void ofApp::mouseMoved(int x, int y ){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseDragged(int x, int y, int button){ + +} + +//-------------------------------------------------------------- +void ofApp::mousePressed(int x, int y, int button){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseReleased(int x, int y, int button){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseEntered(int x, int y){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseExited(int x, int y){ + +} + +//-------------------------------------------------------------- +void ofApp::windowResized(int w, int h){ + mCam.setControlArea( { 0,0,float( w ),float( h ) } ); +} + +//-------------------------------------------------------------- +void ofApp::gotMessage(ofMessage msg){ + +} + +//-------------------------------------------------------------- +void ofApp::dragEvent(ofDragInfo dragInfo){ + +} diff --git a/apps/devApps/vkDemoFlag/src/ofApp.h b/apps/devApps/vkDemoFlag/src/ofApp.h new file mode 100644 index 00000000000..39cc847cabc --- /dev/null +++ b/apps/devApps/vkDemoFlag/src/ofApp.h @@ -0,0 +1,59 @@ +#pragma once + +#include "ofMain.h" +#include "vk/DrawCommand.h" +#include "vk/HelperTypes.h" +#include "vk/BufferAllocator.h" +#include "vk/ImageAllocator.h" +#include "vk/Texture.h" + +class ofApp : public ofBaseApp +{ + + of::vk::DrawCommand flagStripDraw; + of::vk::DrawCommand backGroundDraw; + of::vk::DrawCommand lambertDraw; + + std::shared_ptr mFlagShader; + std::shared_ptr mBgShader; + std::shared_ptr mLambertShader; + + + ofEasyCam mCam; + + void setupStaticGeometry(); + void setupTextureData(); + void setupDrawCommands(); + + of::vk::BufferAllocator mStaticAllocator; + of::vk::ImageAllocator mImageAllocator; + + of::vk::BufferRegion flagVertices; + of::vk::BufferRegion flagTexCoords; + of::vk::BufferRegion flagIndices; + + of::vk::BufferRegion flagPoleVertices; + of::vk::BufferRegion flagPoleNormals; + of::vk::BufferRegion flagPoleIndices; + + std::shared_ptr<::vk::Image> mFlagImage; + of::vk::Texture mFlagTexture; + +public: + void setup(); + void update(); + void draw(); + + void keyPressed( int key ); + void keyReleased( int key ); + void mouseMoved( int x, int y ); + void mouseDragged( int x, int y, int button ); + void mousePressed( int x, int y, int button ); + void mouseReleased( int x, int y, int button ); + void mouseEntered( int x, int y ); + void mouseExited( int x, int y ); + void windowResized( int w, int h ); + void dragEvent( ofDragInfo dragInfo ); + void gotMessage( ofMessage msg ); + +}; diff --git a/apps/devApps/vkDemoFlag/vkDemoFlag.sln b/apps/devApps/vkDemoFlag/vkDemoFlag.sln new file mode 100644 index 00000000000..a7924a53c8e --- /dev/null +++ b/apps/devApps/vkDemoFlag/vkDemoFlag.sln @@ -0,0 +1,35 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vkDemoFlag", "vkDemoFlag.vcxproj", "{7FD42DF7-442E-479A-BA76-D0022F99702A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "openframeworksLib", "..\..\..\libs\openFrameworksCompiled\project\vs\openframeworksLib.vcxproj", "{5837595D-ACA9-485C-8E76-729040CE4B0B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|Win32.ActiveCfg = Debug|Win32 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|Win32.Build.0 = Debug|Win32 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|x64.ActiveCfg = Debug|x64 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|x64.Build.0 = Debug|x64 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|Win32.ActiveCfg = Release|Win32 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|Win32.Build.0 = Release|Win32 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|x64.ActiveCfg = Release|x64 + {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|x64.Build.0 = Release|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|Win32.ActiveCfg = Debug|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|Win32.Build.0 = Debug|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|x64.ActiveCfg = Debug|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|x64.Build.0 = Debug|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|Win32.ActiveCfg = Release|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|Win32.Build.0 = Release|Win32 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|x64.ActiveCfg = Release|x64 + {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/apps/devApps/vkDemoFlag/vkDemoFlag.vcxproj b/apps/devApps/vkDemoFlag/vkDemoFlag.vcxproj new file mode 100644 index 00000000000..dfd1eb7c619 --- /dev/null +++ b/apps/devApps/vkDemoFlag/vkDemoFlag.vcxproj @@ -0,0 +1,198 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {7996322B-3717-4F3D-822A-BD50F0B57178} + Win32Proj + vkDemoFlag + + + + Application + Unicode + v141 + + + Application + Unicode + v141 + + + Application + Unicode + true + v141 + + + Application + Unicode + true + v141 + + + + + + + + + + + + + + + + + + + + + bin\ + obj\$(Configuration)\ + $(ProjectName)_debug + true + true + + + bin\ + obj\$(Configuration)\ + $(ProjectName)_debug + true + true + + + bin\ + obj\$(Configuration)\ + false + + + bin\ + obj\$(Configuration)\ + false + + + + Disabled + EnableFastChecks + %(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + %(AdditionalIncludeDirectories) + CompileAsCpp + + + true + Console + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + Disabled + EnableFastChecks + %(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + %(AdditionalIncludeDirectories) + CompileAsCpp + true + + + true + Console + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + false + %(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + %(AdditionalIncludeDirectories) + CompileAsCpp + true + + + false + false + Console + true + true + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + false + %(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + %(AdditionalIncludeDirectories) + CompileAsCpp + + + false + false + Console + true + true + false + %(AdditionalDependencies) + %(AdditionalLibraryDirectories) + + + + + + + + + + + + + {5837595d-aca9-485c-8e76-729040ce4b0b} + + + + + /D_DEBUG %(AdditionalOptions) + /D_DEBUG %(AdditionalOptions) + $(OF_ROOT)\libs\openFrameworksCompiled\project\vs + + + + + + + + + \ No newline at end of file diff --git a/apps/devApps/vkDemoFlag/vkDemoFlag.vcxproj.filters b/apps/devApps/vkDemoFlag/vkDemoFlag.vcxproj.filters new file mode 100644 index 00000000000..c5325387daa --- /dev/null +++ b/apps/devApps/vkDemoFlag/vkDemoFlag.vcxproj.filters @@ -0,0 +1,24 @@ + + + + + src + + + src + + + + + {d8376475-7454-4a24-b08a-aac121d3ad6f} + + + + + src + + + + + + diff --git a/libs/openFrameworks/app/ofAppBaseWindow.h b/libs/openFrameworks/app/ofAppBaseWindow.h index e5d254b4762..cdbd7e39caf 100644 --- a/libs/openFrameworks/app/ofAppBaseWindow.h +++ b/libs/openFrameworks/app/ofAppBaseWindow.h @@ -128,3 +128,20 @@ class ofAppBaseGLESWindow: public ofAppBaseWindow{ } } }; + +class ofAppBaseVkWindow : public ofAppBaseWindow +{ +public: + virtual ~ofAppBaseVkWindow(){ + } + virtual void setup( const ofVkWindowSettings & settings ) = 0; + void setup( const ofWindowSettings & settings ){ + const ofVkWindowSettings * vkSettings = dynamic_cast( &settings ); + if ( vkSettings ){ + setup( *vkSettings ); + } + else{ + setup( ofVkWindowSettings( settings ) ); + } + } +}; \ No newline at end of file diff --git a/libs/openFrameworks/app/ofAppGLFWWindow.cpp b/libs/openFrameworks/app/ofAppGLFWWindow.cpp index dd83e5abfb2..633312c8c88 100644 --- a/libs/openFrameworks/app/ofAppGLFWWindow.cpp +++ b/libs/openFrameworks/app/ofAppGLFWWindow.cpp @@ -2,8 +2,12 @@ #include "ofEvents.h" #include "ofBaseApp.h" -#include "ofGLRenderer.h" -#include "ofGLProgrammableRenderer.h" +#ifdef OF_TARGET_API_VULKAN + #include "ofVkRenderer.h" +#else + #include "ofGLRenderer.h" + #include "ofGLProgrammableRenderer.h" +#endif // !OF_TARGET_API_VULKAN #include "ofAppRunner.h" #include "ofFileUtils.h" @@ -85,62 +89,28 @@ void ofAppGLFWWindow::close(){ } } -//------------------------------------------------------------ -void ofAppGLFWWindow::setNumSamples(int _samples){ - settings.numSamples=_samples; -} - -//------------------------------------------------------------ -void ofAppGLFWWindow::setMultiDisplayFullscreen(bool bMultiFullscreen){ - settings.multiMonitorFullScreen = bMultiFullscreen; -} - -//------------------------------------------------------------ -void ofAppGLFWWindow::setDoubleBuffering(bool doubleBuff){ - settings.doubleBuffering = doubleBuff; -} - -//------------------------------------------------------------ -void ofAppGLFWWindow::setColorBits(int r, int g, int b){ - settings.redBits=r; - settings.greenBits=g; - settings.blueBits=b; -} - -//------------------------------------------------------------ -void ofAppGLFWWindow::setAlphaBits(int a){ - settings.alphaBits=a; -} - -//------------------------------------------------------------ -void ofAppGLFWWindow::setDepthBits(int depth){ - settings.depthBits=depth; -} - -//------------------------------------------------------------ -void ofAppGLFWWindow::setStencilBits(int stencil){ - settings.stencilBits=stencil; -} - //------------------------------------------------------------ #ifdef TARGET_OPENGLES void ofAppGLFWWindow::setup(const ofGLESWindowSettings & settings){ +#elif defined(OF_TARGET_API_VULKAN) +void ofAppGLFWWindow::setup( const ofVkWindowSettings & settings ){ #else void ofAppGLFWWindow::setup(const ofGLWindowSettings & settings){ #endif - const ofGLFWWindowSettings * glSettings = dynamic_cast(&settings); - if(glSettings){ - setup(*glSettings); + const ofGLFWWindowSettings * glfwSettings = dynamic_cast(&settings); + if( glfwSettings ){ + setup( *glfwSettings ); }else{ - setup(ofGLFWWindowSettings(settings)); + setup(ofGLFWWindowSettings( settings )); } } +//------------------------------------------------------------ void ofAppGLFWWindow::setup(const ofGLFWWindowSettings & _settings){ if(windowP){ ofLogError() << "window already setup, probably you are mixing old and new style setup"; ofLogError() << "call only ofCreateWindow(settings) or ofSetupOpenGL(...)"; - ofLogError() << "calling window->setup() after ofCreateWindow() is not necesary and won't do anything"; + ofLogError() << "calling window->setup() after ofCreateWindow() is not necessary and won't do anything"; return; } settings = _settings; @@ -150,8 +120,64 @@ void ofAppGLFWWindow::setup(const ofGLFWWindowSettings & _settings){ return; } -// ofLogNotice("ofAppGLFWWindow") << "WINDOW MODE IS " << screenMode; +#if defined (OF_TARGET_API_VULKAN) + // initialise Vulkan backed window + if ( glfwVulkanSupported() ){ + ofLog() << "Vulkan supported."; + + // all vulkan setup happens here. + glfwWindowHint( GLFW_CLIENT_API, GLFW_NO_API ); + + windowP = glfwCreateWindow( settings.width, + settings.height, + settings.title.c_str(), + NULL, + NULL ); + if ( !windowP ){ + // It didn't work, so try to give a useful error: + printf( "Cannot create a window in which to draw!\n" ); + fflush( stdout ); + exit( 1 ); + } + + auto vkRenderer = std::shared_ptr( new ofVkRenderer( this, _settings.rendererSettings) ); + currentRenderer = vkRenderer; + + // we have a renderer. + // Now we need to create a window surface + createVkSurface(); + + { + // create swapchain + of::vk::WsiSwapchainSettings swapchainSettings{}; + swapchainSettings.width = _settings.width; + swapchainSettings.height = _settings.height; + swapchainSettings.numSwapchainImages = _settings.rendererSettings.numSwapchainImages; + swapchainSettings.presentMode = _settings.rendererSettings.presentMode; + swapchainSettings.windowSurface = getVkSurface(); + vkRenderer->setSwapchain( std::make_shared( swapchainSettings ) ); + } + + vkRenderer->setup(); + //don't try and show a window if its been requsted to be hidden + bWindowNeedsShowing = settings.visible; + + // set the user window pointer + glfwSetWindowUserPointer( windowP, this ); + + // write the window size back in our settings + glfwGetWindowSize( windowP, &settings.width, &settings.height ); + + currentW = settings.width; + currentH = settings.height; + } + else{ + ofLog() << "Vulkan not supported."; + ofExit(); + } +#else + // initialise OpenGL or OpenGLES backed window glfwDefaultWindowHints(); glfwWindowHint(GLFW_RED_BITS, settings.redBits); glfwWindowHint(GLFW_GREEN_BITS, settings.greenBits); @@ -314,7 +340,10 @@ void ofAppGLFWWindow::setup(const ofGLFWWindowSettings & _settings){ static_cast(currentRenderer.get())->setup(); } - setVerticalSync(true); + setVerticalSync( true ); +#endif // #if !(defined OF_TARGET_API_VULKAN) + + // set GLFW callbacks glfwSetMouseButtonCallback(windowP, mouse_cb); glfwSetCursorPosCallback(windowP, motion_cb); glfwSetCursorEnterCallback(windowP, entry_cb); @@ -345,8 +374,8 @@ void ofAppGLFWWindow::setup(const ofGLFWWindowSettings & _settings){ #endif } -#ifdef TARGET_LINUX //------------------------------------------------------------ +#ifdef TARGET_LINUX void ofAppGLFWWindow::setWindowIcon(const string & path){ ofPixels iconPixels; ofLoadImage(iconPixels,path); @@ -404,6 +433,7 @@ void ofAppGLFWWindow::draw(){ events().notifyDraw(); +#ifndef OF_TARGET_API_VULKAN #ifdef TARGET_WIN32 if (currentRenderer->getBackgroundAuto() == false){ // on a PC resizing a window with this method of accumulation (essentially single buffering) @@ -436,8 +466,8 @@ void ofAppGLFWWindow::draw(){ } else{ glFlush(); } - #endif - + #endif // #ifdef TARGET_WIN32 +#endif // #ifndef OF_TARGET_API_VULKAN currentRenderer->finishRender(); nFramesSinceWindowResized++; @@ -445,9 +475,13 @@ void ofAppGLFWWindow::draw(){ //-------------------------------------------- -void ofAppGLFWWindow::swapBuffers() { - glfwSwapBuffers(windowP); + +#ifndef OF_TARGET_API_VULKAN +void ofAppGLFWWindow::swapBuffers(){ + // OpenGL/GLES uses glfw to handle swapBuffers + glfwSwapBuffers( windowP ); } +#endif //-------------------------------------------- void ofAppGLFWWindow::startRender() { @@ -601,8 +635,10 @@ void ofAppGLFWWindow::setWindowShape(int w, int h){ setWindowPosition(pos.x, pos.y); } #else - glfwSetWindowSize(windowP,currentW,currentH); - #endif + #ifndef OF_TARGET_API_VULKAN + glfwSetWindowSize(windowP,currentW,currentH); + #endif + #endif } //------------------------------------------------------------ @@ -1496,9 +1532,11 @@ void ofAppGLFWWindow::resize_cb(GLFWwindow* windowP_, int w, int h) { instance->windowW = framebufferW; instance->windowH = framebufferH; } - instance->currentW = windowW; instance->currentH = windowH; +#ifdef OF_TARGET_API_VULKAN + dynamic_pointer_cast(instance->renderer())->resizeScreen( w, h ); +#endif instance->events().notifyWindowResized(framebufferW, framebufferH); instance->nFramesSinceWindowResized = 0; } @@ -1516,11 +1554,13 @@ void ofAppGLFWWindow::exit_cb(GLFWwindow* windowP_){ //------------------------------------------------------------ void ofAppGLFWWindow::setVerticalSync(bool bVerticalSync){ +#if !defined( OF_TARGET_API_VULKAN ) if(bVerticalSync){ glfwSwapInterval( 1); }else{ glfwSwapInterval(0); } +#endif // !defined( OF_TARGET_API_VULKAN ) } //------------------------------------------------------------ @@ -1588,11 +1628,41 @@ void ofAppGLFWWindow::iconify(bool bIconify){ glfwRestoreWindow(windowP); } +#ifdef OF_TARGET_API_VULKAN +// Vulkan +//------------------------------------------------------------ +VkResult ofAppGLFWWindow::createVkSurface(){ + // Create window surface for this window through WSI, and + // store surface in mWindowSurface + auto r = dynamic_pointer_cast( currentRenderer ); + return glfwCreateWindowSurface( r->getInstance(), windowP, VK_NULL_HANDLE, &mWindowSurface ); +} + +//------------------------------------------------------------ + +void ofAppGLFWWindow::destroyVkSurface(){ + auto r = dynamic_pointer_cast( currentRenderer ); + + vkDestroySurfaceKHR( r->getInstance(), mWindowSurface, nullptr ); + mWindowSurface = VK_NULL_HANDLE; +} + +//------------------------------------------------------------ +const VkSurfaceKHR& ofAppGLFWWindow::getVkSurface(){ + return mWindowSurface; +}; + + +//------------------------------------------------------------ +#endif // OF_TARGET_API_VULKAN +#ifndef OF_TARGET_API_VULKAN +// OpenGL/OpenGLES void ofAppGLFWWindow::makeCurrent(){ glfwMakeContextCurrent(windowP); } +#endif // !OF_TARGET_API_VULKAN #if defined(TARGET_LINUX) && !defined(TARGET_RASPBERRY_PI) Display* ofAppGLFWWindow::getX11Display(){ diff --git a/libs/openFrameworks/app/ofAppGLFWWindow.h b/libs/openFrameworks/app/ofAppGLFWWindow.h index 1502ff08541..905d654483a 100644 --- a/libs/openFrameworks/app/ofAppGLFWWindow.h +++ b/libs/openFrameworks/app/ofAppGLFWWindow.h @@ -2,7 +2,11 @@ #include "ofConstants.h" -#define GLFW_INCLUDE_NONE +#ifdef OF_TARGET_API_VULKAN + #define GLFW_INCLUDE_VULKAN +#else + #define GLFW_INCLUDE_NONE +#endif #include "GLFW/glfw3.h" #include "ofAppBaseWindow.h" @@ -19,6 +23,8 @@ class ofBaseApp; #ifdef TARGET_OPENGLES class ofGLFWWindowSettings: public ofGLESWindowSettings{ +#elif defined(OF_TARGET_API_VULKAN) +class ofGLFWWindowSettings : public ofVkWindowSettings{ #else class ofGLFWWindowSettings: public ofGLWindowSettings{ #endif @@ -27,12 +33,16 @@ class ofGLFWWindowSettings: public ofGLWindowSettings{ #ifdef TARGET_OPENGLES ofGLFWWindowSettings(const ofGLESWindowSettings & settings) - :ofGLESWindowSettings(settings){} + : ofGLESWindowSettings(settings){} +#elif defined(OF_TARGET_API_VULKAN) + ofGLFWWindowSettings( const ofVkWindowSettings & settings ) + : ofVkWindowSettings( settings ){} #else ofGLFWWindowSettings(const ofGLWindowSettings & settings) - :ofGLWindowSettings(settings){} + : ofGLWindowSettings(settings){} #endif +#ifndef OF_TARGET_API_VULKAN int numSamples = 4; bool doubleBuffering = true; int redBits = 8; @@ -42,17 +52,20 @@ class ofGLFWWindowSettings: public ofGLWindowSettings{ int depthBits = 24; int stencilBits = 0; bool stereo = false; + shared_ptr shareContextWith; +#endif + bool multiMonitorFullScreen = false; bool visible = true; bool iconified = false; bool decorated = true; bool resizable = true; int monitor = 0; - bool multiMonitorFullScreen = false; - std::shared_ptr shareContextWith; }; -#ifdef TARGET_OPENGLES +#if defined(TARGET_OPENGLES) class ofAppGLFWWindow : public ofAppBaseGLESWindow{ +#elif defined(OF_TARGET_API_VULKAN) +class ofAppGLFWWindow : public ofAppBaseVkWindow { #else class ofAppGLFWWindow : public ofAppBaseGLWindow { #endif @@ -77,6 +90,23 @@ class ofAppGLFWWindow : public ofAppBaseGLWindow { using ofAppBaseWindow::setup; #ifdef TARGET_OPENGLES void setup(const ofGLESWindowSettings & settings); +#elif defined(OF_TARGET_API_VULKAN) + + void setup( const ofVkWindowSettings & settings ); + + // Create a vkSurface using GLFW. The surface is owned by the current window. + VkResult createVkSurface(); + + // storage for a pointer to a vkSurface owned by this window + VkSurfaceKHR mWindowSurface; + + // Destroy a vkSurface + void destroyVkSurface(); + + // Return vkSurface used to render to this window + const VkSurfaceKHR& getVkSurface(); + + #else void setup(const ofGLWindowSettings & settings); #endif @@ -127,8 +157,11 @@ class ofAppGLFWWindow : public ofAppBaseGLWindow { int getPixelScreenCoordScale(); +#ifndef OF_TARGET_API_VULKAN void makeCurrent(); void swapBuffers(); +#endif + void startRender(); void finishRender(); @@ -139,16 +172,6 @@ class ofAppGLFWWindow : public ofAppBaseGLWindow { bool isWindowResizeable(); void iconify(bool bIconify); - // window settings, this functions can only be called from main before calling ofSetupOpenGL - // TODO: remove specialized version of ofSetupOpenGL when these go away - OF_DEPRECATED_MSG("use ofGLFWWindowSettings to create the window instead", void setNumSamples(int samples)); - OF_DEPRECATED_MSG("use ofGLFWWindowSettings to create the window instead", void setDoubleBuffering(bool doubleBuff)); - OF_DEPRECATED_MSG("use ofGLFWWindowSettings to create the window instead", void setColorBits(int r, int g, int b)); - OF_DEPRECATED_MSG("use ofGLFWWindowSettings to create the window instead", void setAlphaBits(int a)); - OF_DEPRECATED_MSG("use ofGLFWWindowSettings to create the window instead", void setDepthBits(int depth)); - OF_DEPRECATED_MSG("use ofGLFWWindowSettings to create the window instead", void setStencilBits(int stencil)); - OF_DEPRECATED_MSG("use ofGLFWWindowSettings to create the window instead", void setMultiDisplayFullscreen(bool bMultiFullscreen)); //note this just enables the mode, you have to toggle fullscreen to activate it. - #if defined(TARGET_LINUX) && !defined(TARGET_RASPBERRY_PI) Display* getX11Display(); Window getX11Window(); diff --git a/libs/openFrameworks/app/ofWindowSettings.h b/libs/openFrameworks/app/ofWindowSettings.h index 484760078fb..540fb5e7dde 100644 --- a/libs/openFrameworks/app/ofWindowSettings.h +++ b/libs/openFrameworks/app/ofWindowSettings.h @@ -1,11 +1,13 @@ #pragma once -#include "ofConstants.h" -#include "ofVec2f.h" - #include "ofConstants.h" #include "ofVec2f.h" #include "ofVec3f.h" +#if defined (OF_TARGET_API_VULKAN) +#include +#include "vk/HelperTypes.h" +#endif + class ofWindowSettings{ public: ofWindowSettings() @@ -43,19 +45,19 @@ class ofWindowSettings{ class ofGLWindowSettings: public ofWindowSettings{ public: ofGLWindowSettings() - :glVersionMajor(2) - ,glVersionMinor(1){} + :glVersionMajor(2) + ,glVersionMinor(1){} ofGLWindowSettings(const ofWindowSettings & settings) - :ofWindowSettings(settings) - ,glVersionMajor(2) - ,glVersionMinor(1){ - const ofGLWindowSettings * glSettings = dynamic_cast(&settings); - if(glSettings){ - glVersionMajor = glSettings->glVersionMajor; - glVersionMinor = glSettings->glVersionMinor; - } - } + :ofWindowSettings(settings) + ,glVersionMajor(2) + ,glVersionMinor(1){ + const ofGLWindowSettings * glSettings = dynamic_cast(&settings); + if(glSettings){ + glVersionMajor = glSettings->glVersionMajor; + glVersionMinor = glSettings->glVersionMinor; + } + } virtual ~ofGLWindowSettings(){}; @@ -89,3 +91,24 @@ class ofGLESWindowSettings: public ofWindowSettings{ int glesVersion; }; + +#if defined (OF_TARGET_API_VULKAN) + +class ofVkWindowSettings : public ofWindowSettings +{ +public: + ofVkWindowSettings() + {}; + + ofVkWindowSettings( const ofWindowSettings & settings ) + : ofWindowSettings( settings ){ + }; + + of::vk::RendererSettings rendererSettings; + + virtual ~ofVkWindowSettings(){ + } + +}; + +#endif diff --git a/libs/openFrameworks/math/ofVec4f.h b/libs/openFrameworks/math/ofVec4f.h index 3d9a3c5623b..98340ea9dec 100644 --- a/libs/openFrameworks/math/ofVec4f.h +++ b/libs/openFrameworks/math/ofVec4f.h @@ -43,11 +43,11 @@ class ofVec4f { return (const float *)&x; } - float& operator[]( int n ){ + float& operator[]( size_t n ){ return getPtr()[n]; } - float operator[]( int n ) const { + float operator[]( size_t n ) const { return getPtr()[n]; } diff --git a/libs/openFrameworks/utils/ofConstants.h b/libs/openFrameworks/utils/ofConstants.h index 133eedc1774..f98c0fccad3 100644 --- a/libs/openFrameworks/utils/ofConstants.h +++ b/libs/openFrameworks/utils/ofConstants.h @@ -7,6 +7,8 @@ #define OF_VERSION_PATCH 0 #define OF_VERSION_PRE_RELEASE "master" +#define OF_TARGET_API_VULKAN + //------------------------------- /// \brief Used to represent the available video looping modes. diff --git a/libs/openFrameworks/vk/Allocator.h b/libs/openFrameworks/vk/Allocator.h new file mode 100644 index 00000000000..70275f0a709 --- /dev/null +++ b/libs/openFrameworks/vk/Allocator.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include + +namespace of{ +namespace vk{ + + +class AbstractAllocator +{ + +public: + + struct Settings + { + ~Settings() {} // force vtable; + ::vk::PhysicalDeviceProperties physicalDeviceProperties; + ::vk::PhysicalDeviceMemoryProperties physicalDeviceMemoryProperties; + ::vk::Device device = nullptr; + ::vk::DeviceSize size = 0; // how much memory to reserve on hardware for this allocator + ::vk::MemoryPropertyFlags memFlags = ( ::vk::MemoryPropertyFlagBits::eHostVisible | ::vk::MemoryPropertyFlagBits::eHostCoherent ); + std::vector queueFamilyIndices; + }; + + virtual ~AbstractAllocator() {}; // force vtable + + virtual void reset() = 0; + virtual bool allocate(::vk::DeviceSize byteCount_, ::vk::DeviceSize& offset) = 0; + virtual void swap() = 0; + virtual const ::vk::DeviceMemory& getDeviceMemory() const = 0; + virtual const AbstractAllocator::Settings& getSettings() const = 0; + +}; + +// ---------------------------------------------------------------------- +namespace { + + inline bool getMemoryAllocationInfo( + const ::vk::PhysicalDeviceMemoryProperties& memProps, + const::vk::MemoryRequirements & memReqs, + ::vk::MemoryPropertyFlags memFlags, + ::vk::MemoryAllocateInfo & allocInfo + ){ + if ( !memReqs.size ){ + allocInfo.allocationSize = 0; + allocInfo.memoryTypeIndex = ~0; + return true; + } + + // Find an available memory type that satifies the requested properties. + uint32_t memoryTypeIndex; + for ( memoryTypeIndex = 0; memoryTypeIndex < memProps.memoryTypeCount; ++memoryTypeIndex ){ + if ( ( memReqs.memoryTypeBits & ( 1 << memoryTypeIndex ) ) && + ( memProps.memoryTypes[memoryTypeIndex].propertyFlags & memFlags ) == memFlags ){ + break; + } + } + if ( memoryTypeIndex >= memProps.memoryTypeCount ){ + assert( 0 && "memorytypeindex not found" ); + return false; + } + + allocInfo.allocationSize = memReqs.size; + allocInfo.memoryTypeIndex = memoryTypeIndex; + + return true; + } + +} // end anonymous namespace + + +} // namespace of::vk +} // namespace of diff --git a/libs/openFrameworks/vk/BufferAllocator.cpp b/libs/openFrameworks/vk/BufferAllocator.cpp new file mode 100644 index 00000000000..a550e5570d7 --- /dev/null +++ b/libs/openFrameworks/vk/BufferAllocator.cpp @@ -0,0 +1,152 @@ +#include "vk/BufferAllocator.h" +#include "ofLog.h" + +using namespace std; +using namespace of::vk; + +// ---------------------------------------------------------------------- + +void BufferAllocator::setup(const BufferAllocator::Settings settings){ + + const_cast(mSettings) = settings; + + if ( mSettings.frameCount < 1 ){ + ofLogWarning() << "Allocator: Must have a minimum of 1 frame. Setting frames to 1."; + const_cast( mSettings.frameCount ) = 1; + } + + // we need to find out the min buffer uniform alignment from the + // physical device. + + // make this dependent on the type of buffer this allocator stands for + const_cast<::vk::DeviceSize&>( mAlignment ) = mSettings.physicalDeviceProperties.limits.minUniformBufferOffsetAlignment; + + // make sure reserved memory is multiple of alignment, and that we can fit in the number of requested frames. + const_cast<::vk::DeviceSize&>( mSettings.size ) = mSettings.frameCount * mAlignment * ( ( mSettings.size / mSettings.frameCount + mAlignment - 1 ) / mAlignment ); + + ::vk::BufferCreateInfo bufferCreateInfo; + + bufferCreateInfo + .setSize( mSettings.size) + .setUsage( mSettings.bufferUsageFlags ) + .setSharingMode( ::vk::SharingMode::eExclusive ) + .setQueueFamilyIndexCount(mSettings.queueFamilyIndices.size()) + .setPQueueFamilyIndices(mSettings.queueFamilyIndices.data()) + ; + + // allocate physical memory from device + + // 1. create a buffer + // 2. add backing memory to buffer + + mBuffer = mSettings.device.createBuffer( bufferCreateInfo ); + + // 2.1 Get memory requirements including size, alignment and memory type + ::vk::MemoryRequirements memReqs = mSettings.device.getBufferMemoryRequirements( mBuffer ); + + // 2.2 Get the appropriate memory type for this type of buffer allocation + // Only memory types that are visible to the host and coherent (coherent means they + // appear to the GPU without the need of explicit range flushes) + // Vulkan 1.0 guarantees the presence of at least one host-visible+coherent memory heap. + ::vk::MemoryAllocateInfo allocateInfo; + + bool result = getMemoryAllocationInfo( + mSettings.physicalDeviceMemoryProperties, + memReqs, + mSettings.memFlags, + allocateInfo + ); + + // 2.3 Finally, to the allocation + // todo: check for and recover from allocation errors + mDeviceMemory = mSettings.device.allocateMemory( allocateInfo ); + + // 2.4 Attach memory to buffer (buffer must not be already backed by memory) + mSettings.device.bindBufferMemory( mBuffer, mDeviceMemory, 0 ); + + + mOffsetEnd.clear(); + mOffsetEnd.resize( mSettings.frameCount, 0 ); + + mBaseAddress.clear(); + mBaseAddress.resize( mSettings.frameCount, 0 ); + + if ( mSettings.memFlags & ::vk::MemoryPropertyFlagBits::eHostVisible ){ + // Map full memory range for CPU write access + mBaseAddress[0] = (uint8_t*)mSettings.device.mapMemory( mDeviceMemory, 0, VK_WHOLE_SIZE ); + } else{ + mBaseAddress[0] = 0; + } + + for ( uint32_t i = 1; i != mBaseAddress.size(); ++i ){ + // offset the pointer by full frame sizes + // for base addresses above frame 0 + mBaseAddress[i] = mBaseAddress[0] + i * ( mSettings.size / mSettings.frameCount ); + } + + mCurrentMappedAddress = nullptr; + mCurrentVirtualFrameIdx = 0; +} + +// ---------------------------------------------------------------------- + +void of::vk::BufferAllocator::reset(){ + if ( mDeviceMemory ){ + if ( mSettings.memFlags & ::vk::MemoryPropertyFlagBits::eHostVisible ){ + mSettings.device.unmapMemory( mDeviceMemory ); + } + mSettings.device.freeMemory( mDeviceMemory ); + mDeviceMemory = nullptr; + } + + if ( mBuffer ){ + mSettings.device.destroyBuffer( mBuffer ); + mBuffer = nullptr; + } + + mOffsetEnd.clear(); + mBaseAddress.clear(); + mCurrentMappedAddress = nullptr; + mCurrentVirtualFrameIdx = 0; +} + +// ---------------------------------------------------------------------- +// brief linear allocator +// param byteCount number of bytes to allocate +// param frameIdx current virtual frame index +// returns offset memory offset in bytes relative to start of buffer to reach address +bool BufferAllocator::allocate( ::vk::DeviceSize byteCount_, ::vk::DeviceSize& offset ){ + uint32_t alignedByteCount = mAlignment * ( ( byteCount_ + mAlignment - 1 ) / mAlignment ); + + if ( mOffsetEnd[mCurrentVirtualFrameIdx] + alignedByteCount <= (mSettings.size / mSettings.frameCount) ){ + // write out memory address + mCurrentMappedAddress = mBaseAddress[mCurrentVirtualFrameIdx] + mOffsetEnd[mCurrentVirtualFrameIdx]; + // write out offset + offset = mOffsetEnd[mCurrentVirtualFrameIdx] + mCurrentVirtualFrameIdx * ( mSettings.size / mSettings.frameCount ); + mOffsetEnd[mCurrentVirtualFrameIdx] += alignedByteCount; + // TODO: if you use non-coherent memory you need to invalidate the + // cache for the memory that has been written to. + // What we will realistically do is to flush the full memory range occpuied by a frame + // instead of the list of sub-allocations. + return true; + } else { + ofLogError() << "Buffer Allocator: out of memory"; + // TODO: try to recover by re-allocating or something. + // Whatever we do here, it will be very costly and should + // be avoided. + } + return false; +} + +// ---------------------------------------------------------------------- + +void BufferAllocator::free(){ + mOffsetEnd[mCurrentVirtualFrameIdx] = 0; + mCurrentMappedAddress = nullptr; +} + +// ---------------------------------------------------------------------- + +void BufferAllocator::swap(){ + mCurrentVirtualFrameIdx = ( mCurrentVirtualFrameIdx + 1 ) % mSettings.frameCount; +} diff --git a/libs/openFrameworks/vk/BufferAllocator.h b/libs/openFrameworks/vk/BufferAllocator.h new file mode 100644 index 00000000000..4ddfa663f94 --- /dev/null +++ b/libs/openFrameworks/vk/BufferAllocator.h @@ -0,0 +1,140 @@ +#pragma once + +#include "vk/Allocator.h" +#include "vk/HelperTypes.h" +namespace of{ +namespace vk{ + +// ---------------------------------------------------------------------- + + +/* + BufferAllocator is a simple linear allocator. + + Allocator may have more then one virtual frame, + and only allocations from the current virutal + frame are performed until swap(). + + Allocator may be for transient memory or for + static memory. + + If allocated from Host memory, the allocator + maps a buffer to CPU visible memory for its + whole lifetime. + +*/ + + +class BufferAllocator : public AbstractAllocator +{ + +public: + + struct Settings : public AbstractAllocator::Settings + { + uint32_t frameCount = 1; // number of frames to reserve within this allocator + + ::vk::BufferUsageFlags bufferUsageFlags = ( + ::vk::BufferUsageFlagBits::eIndexBuffer + | ::vk::BufferUsageFlagBits::eUniformBuffer + | ::vk::BufferUsageFlagBits::eVertexBuffer + | ::vk::BufferUsageFlagBits::eTransferSrc + | ::vk::BufferUsageFlagBits::eTransferDst ); + + Settings & setSize( ::vk::DeviceSize size_ ){ + AbstractAllocator::Settings::size = size_; + return *this; + } + Settings & setMemFlags( ::vk::MemoryPropertyFlags flags_ ){ + AbstractAllocator::Settings::memFlags = flags_; + return *this; + } + Settings & setQueueFamilyIndices( const std::vector indices_ ){ + AbstractAllocator::Settings::queueFamilyIndices = indices_; + return *this; + } + Settings & setRendererProperties( const of::vk::RendererProperties& props ){ + AbstractAllocator::Settings::device = props.device; + AbstractAllocator::Settings::physicalDeviceMemoryProperties = props.physicalDeviceMemoryProperties; + AbstractAllocator::Settings::physicalDeviceProperties = props.physicalDeviceProperties; + return *this; + } + Settings & setBufferUsageFlags( const ::vk::BufferUsageFlags& flags ){ + bufferUsageFlags = flags; + return *this; + } + Settings & setFrameCount( uint32_t count_ ){ + frameCount = count_; + return *this; + } + }; + + BufferAllocator() + : mSettings() + {}; + + ~BufferAllocator(){ + mSettings.device.waitIdle(); + reset(); + }; + + /// @detail set up allocator based on Settings and pre-allocate + /// a chunk of GPU memory, and attach a buffer to it + void setup(const BufferAllocator::Settings settings); + + /// @brief free GPU memory and de-initialise allocator + void reset() override; + + /// @brief sub-allocate a chunk of memory from GPU + /// + bool allocate( ::vk::DeviceSize byteCount_, ::vk::DeviceSize& offset ) override; + + void swap() override; + + const ::vk::DeviceMemory& getDeviceMemory() const override; + + /// @brief remove all sub-allocations within the given frame + /// @note this does not free GPU memory, it just marks it as unused + void free(); + + // return address to writeable memory, if this allocator is ready to write. + bool map( void*& pAddr ){ + pAddr = mCurrentMappedAddress; + return ( mCurrentMappedAddress != nullptr ); + }; + + // jump to use next segment assigned to next virtual frame + + const ::vk::Buffer& getBuffer() const{ + return mBuffer; + }; + + const AbstractAllocator::Settings& getSettings() const override{ + return mSettings; + } + +private: + const BufferAllocator::Settings mSettings; + const ::vk::DeviceSize mAlignment = 256; // alignment is calculated on setup - but 256 is a sensible default as it is the largest possible according to spec + + std::vector<::vk::DeviceSize> mOffsetEnd; // next free location for allocations + std::vector mBaseAddress; // base address for mapped memory + + ::vk::Buffer mBuffer = nullptr; // owning + ::vk::DeviceMemory mDeviceMemory = nullptr; // owning + + void* mCurrentMappedAddress = nullptr; // current address for writing + size_t mCurrentVirtualFrameIdx = 0; // currently mapped segment +}; + +// ---------------------------------------------------------------------- + +inline const ::vk::DeviceMemory & of::vk::BufferAllocator::getDeviceMemory() const{ + return mDeviceMemory; +} + +// ---------------------------------------------------------------------- + + +} // namespace of::vk +} // namespace of \ No newline at end of file diff --git a/libs/openFrameworks/vk/ComputeCommand.cpp b/libs/openFrameworks/vk/ComputeCommand.cpp new file mode 100644 index 00000000000..f163f5b628e --- /dev/null +++ b/libs/openFrameworks/vk/ComputeCommand.cpp @@ -0,0 +1,204 @@ +#include "vk/ComputeCommand.h" +#include "vk/spooky/SpookyV2.h" + +using namespace std; +using namespace of::vk; + +// setup all non-transient state for this draw object + +// current ubo values are stored with draw command + +// think about it as immutable DATA versus STATE - we want immutable DATA +// not state. DATA is Plain Old Data - and this is how the draw command +// must store itself. + +// ---------------------------------------------------------------------- + + +void ComputeCommand::setup( const ComputePipelineState& pipelineState ){ + + mPipelineState = pipelineState; + + mDescriptorSetData = mPipelineState.getShader()->getDescriptorSetData(); + mUniformDictionary = mPipelineState.getShader()->getUniformDictionary(); + +} + +// ------------------------------------------------------------ + +void ComputeCommand::commitUniforms( BufferAllocator& alloc ){ + + for ( auto & descriptorSetData : mDescriptorSetData ){ + + auto imgInfoIt = descriptorSetData.imageAttachment.begin(); + auto bufferInfoIt = descriptorSetData.bufferAttachment.begin(); + auto dynamicOffsetsIt = descriptorSetData.dynamicBindingOffsets.begin(); + auto dataIt = descriptorSetData.dynamicUboData.begin(); + + for ( auto & descriptor : descriptorSetData.descriptors ){ + + switch ( descriptor.type ){ + case ::vk::DescriptorType::eSampler: + break; + case ::vk::DescriptorType::eCombinedImageSampler: + { + descriptor.imageView = imgInfoIt->imageView; + descriptor.sampler = imgInfoIt->sampler; + descriptor.imageLayout = imgInfoIt->imageLayout; + imgInfoIt++; + } + break; + case ::vk::DescriptorType::eSampledImage: + break; + case ::vk::DescriptorType::eStorageImage: + break; + case ::vk::DescriptorType::eUniformTexelBuffer: + break; + case ::vk::DescriptorType::eStorageTexelBuffer: + break; + case ::vk::DescriptorType::eUniformBuffer: + break; + case ::vk::DescriptorType::eUniformBufferDynamic: + { + descriptor.buffer = alloc.getBuffer(); + ::vk::DeviceSize offset; + void * dataP = nullptr; + + const auto & dataVec = *dataIt; + const auto & dataRange = dataVec.size(); + + // allocate data on gpu + if ( alloc.allocate( dataRange, offset ) && alloc.map( dataP ) ){ + + // copy data to gpu + memcpy( dataP, dataVec.data(), dataRange ); + + // update dynamic binding offsets for this binding + *dynamicOffsetsIt = offset; + + } else{ + ofLogError() << "commitUniforms: could not allocate transient memory."; + } + dataIt++; + dynamicOffsetsIt++; + descriptor.range = dataRange; + } + break; + case ::vk::DescriptorType::eStorageBuffer: + break; + case ::vk::DescriptorType::eStorageBufferDynamic: + descriptor.buffer = bufferInfoIt->buffer; + descriptor.range = bufferInfoIt->range; + *dynamicOffsetsIt = bufferInfoIt->offset; + + bufferInfoIt++; + dynamicOffsetsIt++; + break; + case ::vk::DescriptorType::eInputAttachment: + break; + default: + break; + } // end switch + + } // end for each descriptor + } // end foreach mDescriptorSetData +} + +// ------------------------------------------------------------ + +void ComputeCommand::submit( Context & context, const glm::uvec3& dims = {256,256,1} ){ + + commitUniforms( context.getAllocator() ); + + auto cmd = context.allocateCommandBuffer(::vk::CommandBufferLevel::ePrimary); + + cmd.begin({ ::vk::CommandBufferUsageFlagBits::eOneTimeSubmit }); + + // current pipeline state for building command buffer - this is based on parsing the drawCommand list + std::unique_ptr boundPipelineState; + + // find out pipeline state needed for this compute command + + if ( !boundPipelineState || *boundPipelineState != mPipelineState ){ + // look up pipeline in pipeline cache + // otherwise, create a new pipeline, then bind pipeline. + + boundPipelineState = std::make_unique( mPipelineState ); + + uint64_t pipelineStateHash = boundPipelineState->calculateHash(); + + auto & currentPipeline = context.borrowPipeline( pipelineStateHash ); + + // TODO: check if something fishy is going on here - it may be + // that the pipeline gets destroyed, or leaked, while it is still in use. + + if ( currentPipeline.get() == nullptr ){ + currentPipeline = + std::shared_ptr<::vk::Pipeline>( new ::vk::Pipeline( boundPipelineState->createPipeline( context.mDevice, context.mSettings.pipelineCache ) ), + [device = context.mDevice]( ::vk::Pipeline*rhs ){ + if ( rhs ){ + ofLog() << "destroy pipeline" << std::hex << *rhs; + device.destroyPipeline( *rhs ); + } + delete rhs; + } ); + } + + cmd.bindPipeline( ::vk::PipelineBindPoint::eCompute, *currentPipeline ); + } + + // ----------| invariant: correct pipeline is bound + + // Match currently bound DescriptorSetLayouts against + // dc pipeline DescriptorSetLayouts + std::vector<::vk::DescriptorSet> boundVkDescriptorSets; + std::vector dynamicBindingOffsets; + + const std::vector & setLayoutKeys = mPipelineState.getShader()->getDescriptorSetLayoutKeys(); + + for ( size_t setId = 0; setId != setLayoutKeys.size(); ++setId ){ + + uint64_t setLayoutKey = setLayoutKeys[setId]; + auto & descriptors = getDescriptorSetData( setId ).descriptors; + const auto desciptorSet = mPipelineState.getShader()->getDescriptorSetLayout( setId ); + // calculate hash of descriptorset, combined with descriptor set sampler state + uint64_t descriptorSetHash = SpookyHash::Hash64( descriptors.data(), descriptors.size() * sizeof( DescriptorSetData_t::DescriptorData_t ), setLayoutKey ); + + // Receive a descriptorSet from the renderContext's cache. + // The renderContext will allocate and initialise a DescriptorSet if none has been found. + const ::vk::DescriptorSet& descriptorSet = context.getDescriptorSet( descriptorSetHash, setId, *desciptorSet, descriptors ); + + boundVkDescriptorSets.emplace_back( descriptorSet ); + + const auto & offsets = getDescriptorSetData( setId ).dynamicBindingOffsets; + + // now append dynamic binding offsets for this set to vector of dynamic offsets for this draw call + dynamicBindingOffsets.insert( dynamicBindingOffsets.end(), offsets.begin(), offsets.end() ); + + } + + // We always bind the full descriptor set. + // Bind uniforms (the first set contains the matrices) + + // bind dc descriptorsets to current pipeline descriptor sets + // make sure dynamic ubos have the correct offsets + if ( !boundVkDescriptorSets.empty() ){ + cmd.bindDescriptorSets( + ::vk::PipelineBindPoint::eCompute, // use compute pipeline + *mPipelineState.getShader()->getPipelineLayout(), // VkPipelineLayout object used to program the bindings. + 0, // firstset: first set index (of the above) to bind to - mDescriptorSet[0] will be bound to pipeline layout [firstset] + boundVkDescriptorSets.size(), // setCount: how many sets to bind + boundVkDescriptorSets.data(), // the descriptor sets to match up with our mPipelineLayout (need to be compatible) + dynamicBindingOffsets.size(), // dynamic offsets count how many dynamic offsets + dynamicBindingOffsets.data() // dynamic offsets for each descriptor + ); + } + + cmd.dispatch( dims.x, dims.y, dims.z ); + + cmd.end(); + + context.submit( std::move( cmd ) ); +} + +// ------------------------------------------------------------ diff --git a/libs/openFrameworks/vk/ComputeCommand.h b/libs/openFrameworks/vk/ComputeCommand.h new file mode 100644 index 00000000000..fa5dd93f23b --- /dev/null +++ b/libs/openFrameworks/vk/ComputeCommand.h @@ -0,0 +1,154 @@ +#pragma once +#include "vulkan/vulkan.hpp" +#include "vk/HelperTypes.h" +#include "vk/Shader.h" +#include "vk/Pipeline.h" +#include "vk/Texture.h" +#include "vk/Context.h" + +namespace of{ +namespace vk{ + +class BufferAllocator; // ffdecl. +class Context; // ffdecl. +class ComputeCommand +{ + +private: + + /* compute pipeline state is essentially just a link to the shader */ + ComputePipelineState mPipelineState; + +private: /* transient data */ + + uint64_t mPipelineHash = 0; + + // Bindings data for descriptorSets, (vector index == set number) - retrieved from shader on setup + std::vector mDescriptorSetData; + + // Lookup table for uniform name-> desciptorSetData - retrieved from shader on setup + std::map mUniformDictionary; + + // set data for upload to ubo - data is stored locally + // until command is submitted + void commitUniforms( BufferAllocator& alloc_ ); + +public: + + void setup( const ComputePipelineState& pipelineState ); + + const ComputePipelineState& getPipelineState() const; + + const DescriptorSetData_t& getDescriptorSetData( size_t setId_ ) const; + + // store uniform values to staging cpu memory + template + ComputeCommand & setUniform( const std::string& uniformName, const T& uniformValue_ ); + + ComputeCommand & setTexture( const std::string& name, const of::vk::Texture& tex_ ); + ComputeCommand & setStorageBuffer( const std::string& name, const of::vk::BufferRegion& buf_ ); + + void submit( of::vk::Context& rc_, const glm::uvec3& dims ); + +}; + +// ------------------------------------------------------------ +// Inline getters and setters + +template +inline ComputeCommand& ComputeCommand::setUniform( const std::string & uniformName, const T & uniformValue_ ){ + + auto uniformInfoIt = mUniformDictionary.find( uniformName ); + + if ( uniformInfoIt == mUniformDictionary.end() ){ + ofLogWarning() << "Could not set Uniform '" << uniformName << "': Uniform name not found in shader"; + return *this; + } + + // --------| invariant: uniform found + + const auto & uniformInfo = uniformInfoIt->second; + + if ( uniformInfo.dataRange < sizeof( T ) ){ + ofLogWarning() << "Could not set uniform '" << uniformName << "': Uniform data size does not match: " + << " Expected: " << uniformInfo.dataRange << ", received: " << sizeof( T ) << "."; + return *this; + } + + // --------| invariant: size match, we can copy data into our vector. + + auto & dataVec = mDescriptorSetData[uniformInfo.setIndex].dynamicUboData[uniformInfo.auxDataIndex]; + + if ( uniformInfo.dataOffset + uniformInfo.dataRange <= dataVec.size() ){ + memcpy( dataVec.data() + uniformInfo.dataOffset, &uniformValue_, uniformInfo.dataRange ); + } else{ + ofLogError() << "Not enough space in local uniform storage. Has this drawCommand been properly initialised?"; + } + + return *this; +} + +// ------------------------------------------------------------ + +inline ComputeCommand & ComputeCommand::setTexture( const std::string & name_, const Texture & tex_ ){ + + auto uniformInfoIt = mUniformDictionary.find( name_ ); + + if ( uniformInfoIt == mUniformDictionary.end() ){ + ofLogWarning() << "Could not set Texture '" << name_ << "': Uniform name not found in shader"; + return *this; + } + + // --------| invariant: uniform found + + const auto & uniformInfo = uniformInfoIt->second; + + auto & imageAttachment = mDescriptorSetData[uniformInfo.setIndex].imageAttachment[uniformInfo.auxDataIndex]; + + imageAttachment.sampler = tex_.getSampler(); + imageAttachment.imageView = tex_.getImageView(); + imageAttachment.imageLayout = tex_.getImageLayout(); + + return *this; +} + +// ------------------------------------------------------------ + +inline ComputeCommand & ComputeCommand::setStorageBuffer( const std::string & uniformName, const of::vk::BufferRegion& buf_ ){ + + auto uniformInfoIt = mUniformDictionary.find( uniformName ); + + if ( uniformInfoIt == mUniformDictionary.end() ){ + ofLogWarning() << "Could not set Storage Buffer '" << uniformName << "': Uniform name not found in shader"; + return *this; + } + + // --------| invariant: uniform found + + const auto & uniformInfo = uniformInfoIt->second; + + auto & bufferAttachment = mDescriptorSetData[uniformInfo.setIndex].bufferAttachment[uniformInfo.auxDataIndex]; + + bufferAttachment = buf_; + + return *this; +} + +// ------------------------------------------------------------ + +inline const ComputePipelineState & ComputeCommand::getPipelineState() const{ + return mPipelineState; +} + +// ------------------------------------------------------------ + +inline const DescriptorSetData_t & ComputeCommand::getDescriptorSetData( size_t setId_ ) const{ + return mDescriptorSetData[setId_]; +} + +// ------------------------------------------------------------ + +} // namespace vk +} // namespace of + + diff --git a/libs/openFrameworks/vk/Context.cpp b/libs/openFrameworks/vk/Context.cpp new file mode 100644 index 00000000000..724af87c9fd --- /dev/null +++ b/libs/openFrameworks/vk/Context.cpp @@ -0,0 +1,640 @@ +#include "vk/Context.h" +#include "vk/TransferBatch.h" +#include "vk/ofVkRenderer.h" + +using namespace std; +using namespace of::vk; + +// ------------------------------------------------------------ +Context::Context( const Settings & settings ) + : mSettings( settings ){ + if ( mSettings.renderer == nullptr ){ + ofLogFatalError() << "You must specify a renderer for a context."; + ofExit(); + } + +} + +// ------------------------------------------------------------ + +Context::~Context(){ + for ( auto & vf : mVirtualFrames ){ + if ( vf.commandPool ){ + mDevice.destroyCommandPool( vf.commandPool ); + } + for ( auto & pool : vf.descriptorPools ){ + mDevice.destroyDescriptorPool( pool ); + } + if ( vf.semaphoreWait ){ + mDevice.destroySemaphore( vf.semaphoreWait ); + } + if ( vf.semaphoreSignalOnComplete ){ + mDevice.destroySemaphore( vf.semaphoreSignalOnComplete ); + } + if ( vf.fence ){ + mDevice.destroyFence( vf.fence ); + } + if ( !vf.frameBuffers.empty() ){ + for ( auto& fb : vf.frameBuffers ){ + mDevice.destroyFramebuffer( fb ); + } + } + } + mVirtualFrames.clear(); + mTransientMemory.reset(); +} + +// ------------------------------------------------------------ + +void Context::setup(){ + + mTransientMemory.setup(mSettings.transientMemoryAllocatorSettings); + mVirtualFrames.resize(mSettings.transientMemoryAllocatorSettings.frameCount); + + mDescriptorPoolSizes.fill(0); + mAvailableDescriptorCounts.fill(0); + + for ( auto &f : mVirtualFrames ){ + if ( mSettings.renderToSwapChain ){ + f.semaphoreWait = mDevice.createSemaphore( {} ); // this semaphore should be owned by the swapchain. + f.semaphoreSignalOnComplete = mDevice.createSemaphore( {} ); + } else{ + f.semaphoreWait = nullptr; + } + f.fence = mDevice.createFence( { ::vk::FenceCreateFlagBits::eSignaled } ); /* Fence starts as "signaled" */ + f.commandPool = mDevice.createCommandPool( { ::vk::CommandPoolCreateFlagBits::eTransient } ); + } + + mCurrentVirtualFrame = mVirtualFrames.size() ^ 1; + + // self-dependency + mSourceContext = this; +} + +// ------------------------------------------------------------ + +const ::vk::Framebuffer& Context::createFramebuffer( const ::vk::FramebufferCreateInfo& createInfo ){ + auto & fbos = mVirtualFrames[mCurrentVirtualFrame].frameBuffers; + + // Create a framebuffer for the current virtual frame, and link it to the current swapchain images. + auto fb = mDevice.createFramebuffer( createInfo ); + + mVirtualFrames[mCurrentVirtualFrame].frameBuffers.emplace_back( fb ); + + return mVirtualFrames[mCurrentVirtualFrame].frameBuffers.back(); +} + +// ------------------------------------------------------------ + +void Context::waitForFence(){ + auto fenceWaitResult = mDevice.waitForFences( { getFence() }, VK_TRUE, 100'000'000 ); + + if ( fenceWaitResult != ::vk::Result::eSuccess ){ + ofLogError() << "Context: Waiting for fence takes too long: " << ::vk::to_string( fenceWaitResult ); + } +} + +// ------------------------------------------------------------ + +void Context::begin(){ + + // Move to the next available virtual frame + swap(); + + // Wait until fence for current virtual frame has been reached by GPU, which + // indicates that all virtual frame resource access has completed, and that + // all resources of this virtual frame may be reset or re-used. + // + waitForFence(); + + mDevice.resetFences( { getFence() } ); + + // Free old command buffers - this is necessary since otherwise you end up with + // leaking them. + if ( !mVirtualFrames[mCurrentVirtualFrame].commandBuffers.empty() ){ + mDevice.freeCommandBuffers( mVirtualFrames[mCurrentVirtualFrame].commandPool, mVirtualFrames[mCurrentVirtualFrame].commandBuffers ); + mVirtualFrames[mCurrentVirtualFrame].commandBuffers.clear(); + } + + mDevice.resetCommandPool( mVirtualFrames[mCurrentVirtualFrame].commandPool, ::vk::CommandPoolResetFlagBits::eReleaseResources ); + + mTransientMemory.free(); + + // clear old frame buffer attachments + for ( auto & fb : mVirtualFrames[mCurrentVirtualFrame].frameBuffers ){ + mDevice.destroyFramebuffer( fb ); + } + mVirtualFrames[mCurrentVirtualFrame].frameBuffers.clear(); + + // re-create descriptor pool for current virtual frame if necessary + updateDescriptorPool(); + +} + +// ------------------------------------------------------------ + +void Context::end(){ + + auto & frame = mVirtualFrames[mCurrentVirtualFrame]; + + const ::vk::Semaphore * waitSemaphore = nullptr; + const ::vk::Semaphore * signalSemaphore = nullptr; + + if ( mSettings.renderToSwapChain ){ + waitSemaphore = &frame.semaphoreWait; + signalSemaphore = &frame.semaphoreSignalOnComplete; + } else{ + // waitSemaphore = &mSourceContext->getSemaphoreSignalOnComplete(); + } + + ::vk::SubmitInfo submitInfo; + // TODO: the number of array elements must correspond to the number of wait semaphores, as each + // mask specifies what the semaphore is waiting for. + std::array<::vk::PipelineStageFlags, 1> wait_dst_stage_mask = { ::vk::PipelineStageFlagBits::eColorAttachmentOutput }; + + + submitInfo + .setWaitSemaphoreCount( ( waitSemaphore ? 1 : 0) ) // set to zero if waitSemaphore was not set + .setPWaitSemaphores( waitSemaphore ) + .setPWaitDstStageMask( wait_dst_stage_mask.data() ) + .setCommandBufferCount( frame.commandBuffers.size() ) + .setPCommandBuffers( frame.commandBuffers.data() ) + .setSignalSemaphoreCount( ( signalSemaphore ? 1 : 0 ) ) + .setPSignalSemaphores( signalSemaphore ) + ; + + mSettings.renderer->submit(mSettings.vkQueueIndex, { submitInfo }, getFence() ); +} + + +// ------------------------------------------------------------ + +void Context::swap(){ + mCurrentVirtualFrame = ( mCurrentVirtualFrame + 1 ) % mSettings.transientMemoryAllocatorSettings.frameCount; + mTransientMemory.swap(); +} + +// ------------------------------------------------------------ + +const::vk::DescriptorSet Context::getDescriptorSet( uint64_t descriptorSetHash, size_t setId, const ::vk::DescriptorSetLayout & setLayout_, const std::vector & descriptors ){ + + auto & currentVirtualFrame = mVirtualFrames[mCurrentVirtualFrame]; + auto & descriptorSetCache = currentVirtualFrame.descriptorSetCache; + + auto cachedDescriptorSetIt = descriptorSetCache.find( descriptorSetHash ); + + if ( cachedDescriptorSetIt != descriptorSetCache.end() ){ + return cachedDescriptorSetIt->second; + } + + // ----------| Invariant: descriptor set has not been found in the cache for the current frame. + + ::vk::DescriptorSet allocatedDescriptorSet = nullptr; + + // find out required pool sizes for this descriptor set + + std::array requiredPoolSizes; + requiredPoolSizes.fill( 0 ); + + for ( const auto & d : descriptors ){ + uint32_t arrayIndex = uint32_t( d.type ); + ++requiredPoolSizes[arrayIndex]; + } + + // First, we have to figure out if the current descriptor pool has enough space available + // over all descriptor types to allocate the desciptors needed to fill the desciptor set requested. + + + // perform lexicographical compare, i.e. compare pairs of corresponding elements + // until the first mismatch + bool poolLargeEnough = ( mAvailableDescriptorCounts >= requiredPoolSizes ); + + if ( poolLargeEnough == false ){ + + // Allocation cannot be made using current descriptorPool (we're out of descriptors) + // + // Allocate a new descriptorpool - and make sure there is enough space to contain + // all new descriptors. + + std::vector<::vk::DescriptorPoolSize> descriptorPoolSizes; + descriptorPoolSizes.reserve( requiredPoolSizes.size() ); + for ( size_t i = VK_DESCRIPTOR_TYPE_BEGIN_RANGE; i != VK_DESCRIPTOR_TYPE_BEGIN_RANGE + VK_DESCRIPTOR_TYPE_RANGE_SIZE; ++i ){ + if ( requiredPoolSizes[i] != 0 ){ + descriptorPoolSizes.emplace_back( ::vk::DescriptorType( i ), requiredPoolSizes[i] ); + } + } + + ::vk::DescriptorPoolCreateInfo descriptorPoolCreateInfo; + descriptorPoolCreateInfo + .setFlags( ::vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet ) + .setMaxSets( 1 ) + .setPoolSizeCount( descriptorPoolSizes.size() ) + .setPPoolSizes( descriptorPoolSizes.data() ) + ; + + auto descriptorPool = mDevice.createDescriptorPool( descriptorPoolCreateInfo ); + + mVirtualFrames[mCurrentVirtualFrame].descriptorPools.push_back( descriptorPool ); + + // this means all descriptor pools are dirty and will have to be re-created with + // more space to accomodate more descriptor sets. + mDescriptorPoolsDirty = -1; + + for ( size_t i = VK_DESCRIPTOR_TYPE_BEGIN_RANGE; i != VK_DESCRIPTOR_TYPE_BEGIN_RANGE + VK_DESCRIPTOR_TYPE_RANGE_SIZE; ++i ){ + mDescriptorPoolSizes[i] += requiredPoolSizes[i]; + // Update number of available descriptors from descriptor pool. + mAvailableDescriptorCounts[i] += requiredPoolSizes[i]; + } + + // Increase maximum number of sets for allocation from descriptor pool + mDescriptorPoolMaxSets += 1; + + } + + // ---------| invariant: currentVirtualFrame.descriptorPools.back() contains a pool large enough to allocate our descriptor set from + + // we are able to allocate from the current descriptor pool + + auto allocInfo = ::vk::DescriptorSetAllocateInfo(); + allocInfo + .setDescriptorPool( currentVirtualFrame.descriptorPools.back() ) + .setDescriptorSetCount( 1 ) + .setPSetLayouts( &setLayout_ ) + ; + + allocatedDescriptorSet = mDevice.allocateDescriptorSets( allocInfo ).front(); + + // decrease number of available descriptors from the pool + for ( size_t i = VK_DESCRIPTOR_TYPE_BEGIN_RANGE; i != VK_DESCRIPTOR_TYPE_BEGIN_RANGE + VK_DESCRIPTOR_TYPE_RANGE_SIZE; ++i ){ + mAvailableDescriptorCounts[i] -= requiredPoolSizes[i]; + } + + // Once desciptor sets have been allocated, we need to write to them using write desciptorset + // to initialise them. + + std::vector<::vk::WriteDescriptorSet> writeDescriptorSets; + + writeDescriptorSets.reserve( descriptors.size() ); + + for ( const auto & d : descriptors ){ + + // ( we cast address to sampler, as the layout of DescriptorData_t::sampler and + // DescriptorData_t::imageLayout is the same as if we had + // a "proper" ::vk::descriptorImageInfo ) + const ::vk::DescriptorImageInfo* descriptorImageInfo = reinterpret_cast( &d.sampler ); + // same with bufferInfo + const ::vk::DescriptorBufferInfo* descriptorBufferInfo = reinterpret_cast( &d.buffer ); + + writeDescriptorSets.emplace_back( + allocatedDescriptorSet, // dstSet + d.bindingNumber, // dstBinding + d.arrayIndex, // dstArrayElement + 1, // descriptorCount + d.type, // descriptorType + descriptorImageInfo, // pImageInfo + descriptorBufferInfo, // pBufferInfo + nullptr // + ); + + } + + mDevice.updateDescriptorSets( writeDescriptorSets, nullptr ); + + // Now store the newly allocated descriptor set in this frame's descriptor set cache + // so it may be re-used. + descriptorSetCache[descriptorSetHash] = allocatedDescriptorSet; + + return allocatedDescriptorSet; +} + +// ------------------------------------------------------------ + +void Context::updateDescriptorPool(){ + + // If current virtual frame descriptorpool is dirty, + // re-allocate frame descriptorpool based on total number + // of descriptorsets enumerated in mDescriptorPoolSizes + // and mDescriptorPoolMaxsets. + + if ( 0 == ( ( 1ULL << mCurrentVirtualFrame ) & mDescriptorPoolsDirty ) ){ + return; + } + + // --------| invariant: Descriptor Pool for the current virtual frame is dirty. + + // Destroy all cached descriptorSets for the current virtual frame, if any + mVirtualFrames[mCurrentVirtualFrame].descriptorSetCache.clear(); + + // Destroy all descriptor pools for the current virtual frame, if any. + // This will free any descriptorSets allocated from these pools. + for ( const auto& d : mVirtualFrames[mCurrentVirtualFrame].descriptorPools ){ + mDevice.destroyDescriptorPool( d ); + } + mVirtualFrames[mCurrentVirtualFrame].descriptorPools.clear(); + + // Re-create descriptor pool for current virtual frame + // based on number of max descriptor pool count + + std::vector<::vk::DescriptorPoolSize> descriptorPoolSizes; + descriptorPoolSizes.reserve( VK_DESCRIPTOR_TYPE_RANGE_SIZE ); + for ( size_t i = VK_DESCRIPTOR_TYPE_BEGIN_RANGE; i != VK_DESCRIPTOR_TYPE_BEGIN_RANGE + VK_DESCRIPTOR_TYPE_RANGE_SIZE; ++i ){ + if ( mDescriptorPoolSizes[i] != 0 ){ + descriptorPoolSizes.emplace_back( ::vk::DescriptorType( i ), mDescriptorPoolSizes[i] ); + } + } + + if ( descriptorPoolSizes.empty() ){ + return; + // TODO: this needs a fix: happens when method is called for the very first time + // with no pool sizes known. + } + + ::vk::DescriptorPoolCreateInfo descriptorPoolCreateInfo; + descriptorPoolCreateInfo + .setMaxSets( mDescriptorPoolMaxSets ) + .setPoolSizeCount( descriptorPoolSizes.size() ) + .setPPoolSizes( descriptorPoolSizes.data() ) + ; + + mVirtualFrames[mCurrentVirtualFrame].descriptorPools.emplace_back( mDevice.createDescriptorPool( descriptorPoolCreateInfo ) ); + + // Reset number of available descriptors for allocation from + // main descriptor pool + mAvailableDescriptorCounts = mDescriptorPoolSizes; + + // Mark descriptor pool for this frame as not dirty + mDescriptorPoolsDirty ^= ( 1ULL << mCurrentVirtualFrame ); + +} + +// ------------------------------------------------------------ + +std::vector Context::storeBufferDataCmd( const std::vector& dataVec, BufferAllocator& targetAllocator ){ + std::vector resultBuffers; + + auto copyRegions = stageBufferData( dataVec, targetAllocator ); + resultBuffers.reserve( copyRegions.size() ); + + const auto targetBuffer = targetAllocator.getBuffer(); + + for ( size_t i = 0; i != copyRegions.size(); ++i ){ + const auto ®ion = copyRegions[i]; + const auto &srcData = dataVec[i]; + BufferRegion bufRegion; + bufRegion.buffer = targetBuffer; + bufRegion.numElements = srcData.numElements; + bufRegion.offset = region.dstOffset; + bufRegion.range = region.size; + resultBuffers.push_back( std::move( bufRegion ) ); + } + + ::vk::DeviceSize firstOffset = copyRegions.front().dstOffset; + ::vk::DeviceSize totalStaticRange = ( copyRegions.back().dstOffset + copyRegions.back().size ) - firstOffset; + + ::vk::CommandBuffer cmd = allocateCommandBuffer( ::vk::CommandBufferLevel::ePrimary ); + + cmd.begin( { ::vk::CommandBufferUsageFlagBits::eOneTimeSubmit } ); + { + + ::vk::BufferMemoryBarrier srcPrepareBarrier; + srcPrepareBarrier + .setSrcAccessMask( ::vk::AccessFlagBits::eHostWrite ) // finish host stage : host write + .setDstAccessMask( ::vk::AccessFlagBits::eTransferRead ) // before transfer stage : transfer-read + .setSrcQueueFamilyIndex( VK_QUEUE_FAMILY_IGNORED ) + .setDstQueueFamilyIndex( VK_QUEUE_FAMILY_IGNORED ) + .setBuffer( mTransientMemory.getBuffer() ) + .setOffset( firstOffset ) + .setSize( totalStaticRange ) + ; + + ::vk::BufferMemoryBarrier targetPrepareBarrier; + targetPrepareBarrier + .setSrcAccessMask( {} ) // finish host stage : no prior access + .setDstAccessMask( ::vk::AccessFlagBits::eTransferWrite ) // before transfer stage : transfer-write + .setSrcQueueFamilyIndex( VK_QUEUE_FAMILY_IGNORED ) + .setDstQueueFamilyIndex( VK_QUEUE_FAMILY_IGNORED ) + .setBuffer( targetAllocator.getBuffer() ) + .setOffset( firstOffset ) + .setSize( totalStaticRange ) + ; + + // Issue two pipeline barriers to make sure that: + // + // + Source (transient) buffer is in transfer read stage + // + Target (device-only) buffer is in transfer write stage + + cmd.pipelineBarrier( ::vk::PipelineStageFlagBits::eHost, ::vk::PipelineStageFlagBits::eTransfer, {}, {}, { srcPrepareBarrier, targetPrepareBarrier }, {} ); + + cmd.copyBuffer( mTransientMemory.getBuffer(), targetAllocator.getBuffer(), copyRegions ); + + ::vk::BufferMemoryBarrier bufferTransferBarrier; + bufferTransferBarrier + .setSrcAccessMask( ::vk::AccessFlagBits::eTransferWrite ) + .setDstAccessMask( ::vk::AccessFlagBits::eShaderRead | ::vk::AccessFlagBits::eMemoryRead | ::vk::AccessFlagBits::eShaderWrite ) + .setSrcQueueFamilyIndex( VK_QUEUE_FAMILY_IGNORED ) + .setDstQueueFamilyIndex( VK_QUEUE_FAMILY_IGNORED ) + .setBuffer( targetAllocator.getBuffer() ) + .setOffset( firstOffset ) + .setSize( totalStaticRange ) + ; + + // Issue pipeline barrier to make sure that: + // + // Transfer write has completed before any shader operations access the data + + cmd.pipelineBarrier( + ::vk::PipelineStageFlagBits::eTransfer , + ::vk::PipelineStageFlagBits::eFragmentShader | ::vk::PipelineStageFlagBits::eVertexShader | ::vk::PipelineStageFlagBits::eComputeShader, + ::vk::DependencyFlagBits(), + {}, /* no fence */ + { bufferTransferBarrier }, /* buffer barriers */ + {} /* image barriers */ + ); + } + cmd.end(); + + // Submit copy command buffer to current context + // This needs to happen before first draw calls are submitted for the frame. + submit( std::move( cmd ) ); + + return resultBuffers; +} + +// ------------------------------------------------------------ + +std::shared_ptr<::vk::Image> Context::storeImageCmd( const ImageTransferSrcData& data, ImageAllocator& targetImageAllocator ){ + + /* + + These are the steps needed to upload an image: + + 1. load image pixels and image subresource pixels + 2. create vkImage + 3. create vkImageView + 4. aggregate layers and mipmap data into structure of + VkBufferImageCopy + 5. copy layers and mipmap data into contiguous RAM memory = ImageMemBlob + 6. copy ImageMemBlob to (transient) staging memory + 7. use command-buffer copy to copy image + 7.1 layout transition barrier of image for copy + 7.2 vkCmdCopyBuffer image + 7.3 layout transition of image for use by shader (shader read) + 7.4 execute command buffer + + */ + + auto image = std::shared_ptr<::vk::Image>( new ::vk::Image(), [device = mDevice]( ::vk::Image * lhs ){ + if ( lhs ){ + if ( *lhs ){ + device.destroyImage( *lhs ); + } + delete lhs; + } + } ); + + + ::vk::ImageCreateInfo imageCreateInfo; + imageCreateInfo + .setImageType( data.imageType ) + .setFormat( data.format ) + .setExtent( data.extent ) + .setMipLevels( data.mipLevels ) + .setArrayLayers( data.arrayLayers ) + .setSamples( data.samples ) + .setTiling( ::vk::ImageTiling::eOptimal ) + .setUsage( ::vk::ImageUsageFlagBits::eSampled | ::vk::ImageUsageFlagBits::eTransferDst ) + .setSharingMode( ::vk::SharingMode::eExclusive ) + .setQueueFamilyIndexCount( 0 ) + .setPQueueFamilyIndices( nullptr ) + .setInitialLayout( ::vk::ImageLayout::eUndefined ) + ; + + *image = mDevice.createImage( imageCreateInfo ); + + ::vk::DeviceSize numBytes = mDevice.getImageMemoryRequirements( *image ).size; + + ::vk::DeviceSize dstOffset = 0; + if ( targetImageAllocator.allocate( numBytes, dstOffset ) ){ + mDevice.bindImageMemory( *image, targetImageAllocator.getDeviceMemory(), dstOffset ); + } else{ + ofLogError() << "Image Allocation failed."; + image.reset(); + return image; + } + + // --------| invariant: target allocation successful + + ::vk::DeviceSize transientBufferOffset = 0; // location where image data is stored temporarily in transient buffer + void * pData; + if ( mTransientMemory.allocate( data.numBytes, transientBufferOffset ) + && mTransientMemory.map(pData)){ + memcpy( pData, data.pData, data.numBytes ); + } else{ + ofLogError() << "Transient Image data allocation failed."; + image.reset(); + return image; + } + + ::vk::ImageSubresourceLayers subresourceLayers; + subresourceLayers + .setAspectMask(::vk::ImageAspectFlagBits::eColor) + .setMipLevel( 0 ) + .setBaseArrayLayer( 0) + .setLayerCount( 1 ) + ; + + ::vk::BufferImageCopy bufferImageCopy; + bufferImageCopy + .setBufferOffset( transientBufferOffset ) // must be a multiple of four + .setBufferRowLength( data.extent.width ) // must be 0, or greater or equal to imageExtent.width + .setBufferImageHeight( data.extent.height ) + .setImageSubresource( subresourceLayers ) + .setImageOffset( {0,0,0} ) + .setImageExtent( data.extent ) + ; + + ::vk::ImageSubresourceRange subresourceRange; + subresourceRange + .setAspectMask( ::vk::ImageAspectFlagBits::eColor ) + .setBaseMipLevel( 0 ) + .setLevelCount( 1 ) + .setBaseArrayLayer( 0 ) + .setLayerCount( 1 ) + ; + + ::vk::BufferMemoryBarrier bufferTransferBarrier; + bufferTransferBarrier + .setSrcAccessMask( ::vk::AccessFlagBits::eHostWrite ) // after host write + .setDstAccessMask( ::vk::AccessFlagBits::eTransferRead ) // ready buffer for transfer read + .setSrcQueueFamilyIndex( VK_QUEUE_FAMILY_IGNORED ) + .setDstQueueFamilyIndex( VK_QUEUE_FAMILY_IGNORED ) + .setBuffer( mTransientMemory.getBuffer() ) + .setOffset( bufferImageCopy.bufferOffset ) + .setSize( numBytes ) + ; + + ::vk::ImageMemoryBarrier imageLayoutToTransferDstOptimal; + imageLayoutToTransferDstOptimal + .setSrcAccessMask( { } ) // no prior access + .setDstAccessMask( ::vk::AccessFlagBits::eTransferWrite ) // ready image for transferwrite + .setOldLayout( ::vk::ImageLayout::eUndefined ) // from don't care + .setNewLayout( ::vk::ImageLayout::eTransferDstOptimal ) // to transfer destination optimal + .setSrcQueueFamilyIndex( VK_QUEUE_FAMILY_IGNORED ) + .setDstQueueFamilyIndex( VK_QUEUE_FAMILY_IGNORED ) + .setImage( *image ) + .setSubresourceRange( subresourceRange ) + ; + + + ::vk::CommandBuffer cmd = allocateCommandBuffer( ::vk::CommandBufferLevel::ePrimary ); + + cmd.begin( { ::vk::CommandBufferUsageFlagBits::eOneTimeSubmit } ); + + cmd.pipelineBarrier ( + ::vk::PipelineStageFlagBits::eHost, + ::vk::PipelineStageFlagBits::eTransfer, + {}, + {}, + { bufferTransferBarrier }, // buffer: host write -> transfer read + { imageLayoutToTransferDstOptimal } // image: prepare for transfer write + ); + + cmd.copyBufferToImage( mTransientMemory.getBuffer(), *image, ::vk::ImageLayout::eTransferDstOptimal, bufferImageCopy ); + + ::vk::ImageMemoryBarrier imageStagingBarrier; + imageStagingBarrier + .setSrcAccessMask( ::vk::AccessFlagBits::eTransferWrite ) // after transfer write + .setDstAccessMask( ::vk::AccessFlagBits::eShaderRead ) // ready image for shader read + .setOldLayout( ::vk::ImageLayout::eTransferDstOptimal ) // from transfer dst optimal + .setNewLayout( ::vk::ImageLayout::eShaderReadOnlyOptimal ) // to shader readonly optimal + .setSrcQueueFamilyIndex( VK_QUEUE_FAMILY_IGNORED ) + .setDstQueueFamilyIndex( VK_QUEUE_FAMILY_IGNORED ) + .setImage( *image ) + .setSubresourceRange( subresourceRange ) + ; + + cmd.pipelineBarrier( + ::vk::PipelineStageFlagBits::eTransfer, + ::vk::PipelineStageFlagBits::eVertexShader | ::vk::PipelineStageFlagBits::eComputeShader, + {}, + {}, + {}, + { imageStagingBarrier } + ); + + cmd.end(); + + // Submit copy command buffer to current context + // This needs to happen before first draw calls are submitted for the frame. + submit( std::move( cmd ) ); + + return image; +} + +// ------------------------------------------------------------ + +void Context::addContextDependency( Context * ctx ){ + mSourceContext = ctx; +} + +// ------------------------------------------------------------ diff --git a/libs/openFrameworks/vk/Context.h b/libs/openFrameworks/vk/Context.h new file mode 100644 index 00000000000..03a93f9c0ee --- /dev/null +++ b/libs/openFrameworks/vk/Context.h @@ -0,0 +1,276 @@ +#pragma once + +#include +#include "ofLog.h" +#include "vk/HelperTypes.h" +#include "vk/BufferAllocator.h" +#include "vk/ImageAllocator.h" +#include +#include + +/* + +A Context acts as an accumulator and owner for Renderbatches +it safely manages memory by accumulating commandBuffers and their +dependent data in virtual frames. + +It abstracts the swapchain. + +MISSION: + + A Context needs to be able to live within its own thread - + A Context needs to have its own pools, + and needs to be thread-safe. + + One or more batches may submit into a rendercontext - the render- + context will accumulate vkCommandbuffers, and will submit them + on end. + + A Context is the OWNER of all resources used to draw within + one thread. + +*/ + +class ofVkRenderer; // ffdecl. + +namespace of{ +namespace vk{ + +class RenderBatch; // ffdecl. +class ComputeCommand; + +// ------------------------------------------------------------ + +class Context +{ + friend RenderBatch; + friend ComputeCommand; +public: + struct Settings + { + ofVkRenderer * renderer = nullptr; + BufferAllocator::Settings transientMemoryAllocatorSettings; + std::shared_ptr<::vk::PipelineCache> pipelineCache; + bool renderToSwapChain = false; // whether this rendercontext renders to swapchain + size_t vkQueueIndex = 0; // default to 0, as this is presumed a graphics context for a graphics queue + }; + +private: + + const Settings mSettings; + const ::vk::Device& mDevice = mSettings.transientMemoryAllocatorSettings.device; + + struct VirtualFrame + { + ::vk::QueryPool queryPool; + ::vk::CommandPool commandPool; + std::vector<::vk::CommandBuffer> commandBuffers; + std::list<::vk::Framebuffer> frameBuffers; + ::vk::ImageView swapchainImageView; // image attachment to render to swapchain + std::list<::vk::DescriptorPool> descriptorPools; + std::map descriptorSetCache; + + ::vk::Semaphore semaphoreWait; // only used if renderContext renders to swapchain + ::vk::Semaphore semaphoreSignalOnComplete; // semaphore will signal when work complete + + // The most important element in here is the fence, as it protects + // all resources above from being overwritten while still in flight. + // The fence is placed in the command stream upon queue submit, and + // it is waited upon in begin(). This ensures all resources for + // this virtual frame are available and the GPU is finished using + // them for rendering / presenting. + ::vk::Fence fence; + }; + + std::vector mVirtualFrames; + size_t mCurrentVirtualFrame = 0; + + + mutable of::vk::BufferAllocator mTransientMemory; + + // Max number of descriptors per type + // Array index == descriptor type + std::array mDescriptorPoolSizes; + + // Number of descriptors left available for allocation from mDescriptorPool. + // Array index == descriptor type + std::array mAvailableDescriptorCounts; + + // Max number of sets which can be allocated from the main per-frame descriptor pool + uint32_t mDescriptorPoolMaxSets = 0; + + // Bitfield indicating whether the descriptor pool for a virtual frame is dirty + // Each bit represents a virtual frame index. + // We're not expecting more than 64 virtual frames (more than 3 seldom make sense) + uint64_t mDescriptorPoolsDirty = 0; + + // Re-consolidate descriptor pools if necessary + void updateDescriptorPool(); + + // Fetch descriptor either from cache - or allocate and initialise a descriptor based on DescriptorSetData. + const ::vk::DescriptorSet getDescriptorSet( uint64_t descriptorSetHash, size_t setId, const ::vk::DescriptorSetLayout & setLayout_, const std::vector & descriptors ); + + // Cache for all pipelines ever used within this context + std::map> mPipelineCache; + + void waitForFence(); + + std::shared_ptr<::vk::Pipeline>& borrowPipeline( uint64_t pipelineHash ){ + return mPipelineCache[pipelineHash]; + }; + + + // Move to next virtual frame - called internally in begin() after fence has been cleared. + void swap(); + +public: + + Context( const Settings& settings ); + ~Context(); + + const ::vk::Fence & getFence() const ; + const ::vk::Semaphore & getSemaphoreWait() const ; + const ::vk::Semaphore & getSemaphoreSignalOnComplete() const ; + + const size_t getNumVirtualFrames() const; + + // Creates and returns a reference to a temporary framebuffer based on createInfo + const::vk::Framebuffer & createFramebuffer( const::vk::FramebufferCreateInfo & createInfo ); + + // Stages data for copying into targetAllocator's address space + // allocates identical memory chunk in local transient allocator and in targetAllocator + // use BufferCopy vec and a vkCmdBufferCopy to execute copy instruction using a command buffer. + ::vk::BufferCopy stageBufferData( const TransferSrcData& data, BufferAllocator &targetAllocator ); + + std::vector<::vk::BufferCopy> stageBufferData( const std::vector& dataVec, BufferAllocator &targetAllocator ); + + std::vector storeBufferDataCmd( const std::vector& dataVec, BufferAllocator &targetAllocator ); + + std::shared_ptr<::vk::Image> storeImageCmd( const ImageTransferSrcData& data, ImageAllocator& targetImageAllocator ); + + // Create and return command buffer. + // Lifetime is limited to current frame. + // It *must* be submitted to this context within the same frame, that is, before swap(). + ::vk::CommandBuffer allocateCommandBuffer(const ::vk::CommandBufferLevel & commandBufferLevel = ::vk::CommandBufferLevel::ePrimary ) const; + + BufferAllocator & getAllocator() const; + + const ::vk::Device & getDevice() const{ + return mDevice; + }; + + // stores the image view of the current swapchain image into the current virtual frame + void setSwapchainImageView( const ::vk::ImageView& view_ ); + + // return the image view which represents the current swapchain image + const::vk::ImageView & getSwapchainImageView(); + + void setup(); + void begin(); + + // Move command buffer to the rendercontext for batched submission + void submit( ::vk::CommandBuffer&& commandBuffer ); + + // submit all accumulated command buffers to vulkan draw queue for rendering + // this is where semaphore synchronisation happens. Must be matched by begin() + void end(); + + + // context which must be waited upon before this context can render + Context* mSourceContext = nullptr ; + + // define this context to be dependent on another context to be finished rendering first + void addContextDependency( Context* ctx ); + +}; + +// ------------------------------------------------------------ + +inline void Context::submit(::vk::CommandBuffer && commandBuffer) { + mVirtualFrames.at( mCurrentVirtualFrame ).commandBuffers.emplace_back(std::move(commandBuffer)); +} + + +inline const ::vk::Fence & Context::getFence() const { + return mVirtualFrames.at( mCurrentVirtualFrame ).fence; +} + +inline const ::vk::Semaphore & Context::getSemaphoreWait() const { + return mVirtualFrames.at( mCurrentVirtualFrame ).semaphoreWait; +} + +inline const ::vk::Semaphore & Context::getSemaphoreSignalOnComplete() const { + return mVirtualFrames.at( mCurrentVirtualFrame ).semaphoreSignalOnComplete; +} + +inline const size_t Context::getNumVirtualFrames() const{ + return mVirtualFrames.size(); +} + +inline BufferAllocator & Context::getAllocator() const{ + return mTransientMemory; +} + +inline void Context::setSwapchainImageView( const::vk::ImageView & view_ ){ + mVirtualFrames[mCurrentVirtualFrame].swapchainImageView = view_; +} + +inline const::vk::ImageView & Context::getSwapchainImageView(){ + return mVirtualFrames[mCurrentVirtualFrame].swapchainImageView ; +} + +// ------------------------------------------------------------ + +inline std::vector<::vk::BufferCopy> Context::stageBufferData( const std::vector& dataVec, BufferAllocator& targetAllocator ) +{ + std::vector<::vk::BufferCopy> regions; + regions.reserve( dataVec.size()); + + for (const auto & data : dataVec ){ + regions.emplace_back(stageBufferData( data, targetAllocator )); + } + + return regions; +} + +// ------------------------------------------------------------ + +inline ::vk::BufferCopy Context::stageBufferData( const TransferSrcData& data, BufferAllocator& targetAllocator ){ + ::vk::BufferCopy region{ 0, 0, 0 }; + + region.size = data.numBytesPerElement * data.numElements; + + void * pData; + if ( targetAllocator.allocate( region.size, region.dstOffset ) + && mTransientMemory.allocate( region.size, region.srcOffset ) + && mTransientMemory.map( pData ) + ){ + + memcpy( pData, data.pData, region.size ); + + } else{ + ofLogError() << "StageBufferData: Alloc error"; + } + return region; +} + +// ------------------------------------------------------------ + +inline ::vk::CommandBuffer Context::allocateCommandBuffer ( + const ::vk::CommandBufferLevel & commandBufferLevel) const { + ::vk::CommandBuffer cmd; + + ::vk::CommandBufferAllocateInfo commandBufferAllocateInfo; + commandBufferAllocateInfo + .setCommandPool( mVirtualFrames[mCurrentVirtualFrame].commandPool ) + .setLevel( commandBufferLevel ) + .setCommandBufferCount( 1 ) + ; + + mDevice.allocateCommandBuffers( &commandBufferAllocateInfo, &cmd ); + + return cmd; +} + +} // end namespace of::vk +} // end namespace of diff --git a/libs/openFrameworks/vk/DrawCommand.cpp b/libs/openFrameworks/vk/DrawCommand.cpp new file mode 100644 index 00000000000..a71b91f3585 --- /dev/null +++ b/libs/openFrameworks/vk/DrawCommand.cpp @@ -0,0 +1,261 @@ +#include "vk/DrawCommand.h" +#include "vk/RenderBatch.h" +#include "vk/Shader.h" + +using namespace std; +using namespace of::vk; + +// setup all non-transient state for this draw object + +// current ubo values are stored with draw command + +// think about it as immutable DATA versus STATE - we want immutable DATA +// not state. DATA is Plain Old Data - and this is how the draw command +// must store itself. + +// ---------------------------------------------------------------------- + + +void DrawCommand::setup( const GraphicsPipelineState& pipelineState ){ + + if ( !pipelineState.getShader() ){ + ofLogError() << "Cannot setup draw command without valid shader inside pipeline."; + return; + } + + // --------| invariant: pipeline has shader + + mPipelineState = pipelineState; + + mDescriptorSetData = mPipelineState.getShader()->getDescriptorSetData(); + mUniformDictionary = &mPipelineState.getShader()->getUniformDictionary(); + mUniformBindings = &mPipelineState.getShader()->getUniformBindings(); + + // parse shader info to find out how many buffers to reserve for vertex attributes. + + const auto & vertexInfo = mPipelineState.getShader()->getVertexInfo(); + + size_t numVertexBindings = vertexInfo.bindingDescription.size(); + + mVertexBuffers.resize( numVertexBindings, nullptr ); + mVertexOffsets.resize( numVertexBindings, 0 ); + +} + +// ------------------------------------------------------------ + +void DrawCommand::commitUniforms( BufferAllocator& alloc ){ + + for ( auto & descriptorSetData : mDescriptorSetData ){ + + auto imgInfoIt = descriptorSetData.imageAttachment.begin(); + auto bufferInfoIt = descriptorSetData.bufferAttachment.begin(); + auto dynamicOffsetsIt = descriptorSetData.dynamicBindingOffsets.begin(); + auto dataIt = descriptorSetData.dynamicUboData.begin(); + + for ( auto & descriptor : descriptorSetData.descriptors ){ + + switch ( descriptor.type ){ + case ::vk::DescriptorType::eSampler: + break; + case ::vk::DescriptorType::eCombinedImageSampler: + { + descriptor.imageView = imgInfoIt->imageView; + descriptor.sampler = imgInfoIt->sampler; + descriptor.imageLayout = imgInfoIt->imageLayout; + imgInfoIt++; + } + break; + case ::vk::DescriptorType::eSampledImage: + break; + case ::vk::DescriptorType::eStorageImage: + break; + case ::vk::DescriptorType::eUniformTexelBuffer: + break; + case ::vk::DescriptorType::eStorageTexelBuffer: + break; + case ::vk::DescriptorType::eUniformBuffer: + break; + case ::vk::DescriptorType::eUniformBufferDynamic: + { + descriptor.buffer = alloc.getBuffer(); + ::vk::DeviceSize offset; + void * dataP = nullptr; + + const auto & dataVec = *dataIt; + const auto & dataRange = dataVec.size(); + + // allocate memory on gpu + if ( alloc.allocate( dataRange, offset ) && alloc.map( dataP ) ){ + + // copy data from draw command temp storage to gpu + memcpy( dataP, dataVec.data(), dataRange ); + + // update dynamic binding offsets for this binding + *dynamicOffsetsIt = offset; + + } else{ + ofLogError() << "commitUniforms: could not allocate transient memory."; + } + dataIt++; + dynamicOffsetsIt++; + descriptor.range = dataRange; + } + break; + case ::vk::DescriptorType::eStorageBuffer: + break; + case ::vk::DescriptorType::eStorageBufferDynamic: + { + descriptor.buffer = bufferInfoIt->buffer; + descriptor.range = bufferInfoIt->range; + *dynamicOffsetsIt = bufferInfoIt->offset; + + bufferInfoIt++; + dynamicOffsetsIt++; + } + break; + case ::vk::DescriptorType::eInputAttachment: + break; + default: + break; + } // end switch + + } // end for each descriptor + } // end foreach mDescriptorSetData +} + +// ------------------------------------------------------------ + +void DrawCommand::commitMeshAttributes( BufferAllocator& alloc ){ + // check if current draw command has a mesh - if yes, upload mesh data to buffer memory. + if ( mMsh ){ + auto &mesh = *mMsh; + + if ( !mesh.hasVertices() ){ + ofLogError() << "Mesh has no vertices."; + return; + } else{ + allocAndSetAttribute( "inPos", mesh.getVertices(), alloc ); + mNumVertices = uint32_t( mesh.getVertices().size() ); + } + + if ( mesh.hasColors() && mesh.usingColors() ){ + allocAndSetAttribute( "inColor", mesh.getColors(), alloc ); + } + if ( mesh.hasNormals() && mesh.usingNormals() ){ + allocAndSetAttribute( "inNormal", mesh.getNormals(), alloc ); + } + if ( mesh.hasTexCoords() && mesh.usingTextures() ){ + allocAndSetAttribute( "inTexCoord", mesh.getTexCoords(), alloc ); + } + + if ( mesh.hasIndices() && mesh.usingIndices() && mDrawMethod == DrawMethod::eIndexed){ + const auto & indices = mesh.getIndices(); + const auto byteSize = sizeof( indices[0] ) * indices.size(); + + void * dataP = nullptr; + ::vk::DeviceSize offset = 0; + + if ( alloc.allocate( byteSize, offset ) && alloc.map( dataP ) ){ + memcpy( dataP, indices.data(), byteSize ); + setIndices( alloc.getBuffer(), offset ); + mNumIndices = uint32_t( indices.size() ); + } + + } else{ + mIndexBuffer = nullptr; + mIndexOffsets = 0; + } + + } +} + +// ------------------------------------------------------------ + +DrawCommand & DrawCommand::setMesh( const shared_ptr & msh_ ){ + mMsh = msh_; + return *this; +} + +// ------------------------------------------------------------ + +template +DrawCommand & DrawCommand::allocAndSetAttribute( const std::string & attrName_, const std::vector& vec, BufferAllocator& alloc ){ + + size_t index = 0; + + if ( mPipelineState.getShader()->getAttributeBinding( attrName_, index ) ){ + return allocAndSetAttribute( index, vec, alloc ); + } + + // --------| invariant: name was not resolved successfully. + + ofLogWarning() + << "Attribute '" << attrName_ << "' could not be found in shader: " + << mPipelineState.getShader()->getName(); + + return *this; +} + +// ------------------------------------------------------------ + +template +DrawCommand & DrawCommand::allocAndSetAttribute( const std::string & attrName_, const T * data, size_t numBytes, BufferAllocator& alloc ){ + + size_t index = 0; + + if ( mPipelineState.getShader()->getAttributeBinding( attrName_, index ) ){ + return allocAndSetAttribute( index, data, numBytes, alloc ); + } + + // --------| invariant: name was not resolved successfully. + + ofLogWarning() + << "Attribute '" << attrName_ << "' could not be found in shader: " + << mPipelineState.getShader()->getName(); + + return *this; +} + +// ------------------------------------------------------------ +// upload vertex data to gpu memory +template +DrawCommand & DrawCommand::allocAndSetAttribute( const size_t& attribLocation_, const std::vector& vec, BufferAllocator& alloc ){ + const auto numBytes = sizeof( vec[0] ) * vec.size(); + return allocAndSetAttribute(attribLocation_, vec.data(), numBytes, alloc); +} + +// ------------------------------------------------------------ +// upload vertex data to gpu memory +DrawCommand & DrawCommand::allocAndSetAttribute( const size_t& attribLocation_, const void * data, size_t numBytes, BufferAllocator& alloc ){ + + void * dataP = nullptr; + ::vk::DeviceSize offset = 0; + // allocate data on gpu + if ( alloc.allocate( numBytes, offset ) && alloc.map( dataP ) ){ + memcpy( dataP, data, numBytes ); + return setAttribute( attribLocation_, alloc.getBuffer(), offset ); + } + + ofLogWarning() << "Could not allocate memory for attribLocation: " << attribLocation_; + + return *this; +} +// ------------------------------------------------------------ + +DrawCommand & DrawCommand::allocAndSetIndices( const ofIndexType * data, size_t numBytes, BufferAllocator& alloc ){ + + void * dataP = nullptr; + ::vk::DeviceSize offset = 0; + + // allocate data on gpu + + if ( alloc.allocate( numBytes, offset ) && alloc.map( dataP ) ){ + memcpy( dataP, data, numBytes ); + return setIndices( alloc.getBuffer(), offset ); + } + + ofLogWarning() << "Could not allocate memory for indices. "; + + return *this; +} diff --git a/libs/openFrameworks/vk/DrawCommand.h b/libs/openFrameworks/vk/DrawCommand.h new file mode 100644 index 00000000000..29a8cfd3240 --- /dev/null +++ b/libs/openFrameworks/vk/DrawCommand.h @@ -0,0 +1,437 @@ +#pragma once +#include "vulkan/vulkan.hpp" +#include "vk/Shader.h" +#include "vk/Pipeline.h" +#include "vk/HelperTypes.h" +#include "vk/Texture.h" +#include "ofMesh.h" + + +namespace of{ +namespace vk{ + +class DrawCommand; // ffdecl. +class RenderBatch; // ffdecl. +class BufferAllocator; // ffdecl. + +// ---------------------------------------------------------------------- + +class DrawCommand +{ + friend class RenderBatch; + +public : + + // draw mode to be used when submitting DrawCommand + enum class DrawMethod : uint32_t + { + eDraw = 0, // Default method + eIndexed , // Indexed draw + eIndirect , // todo: implement + eIndexedIndirect, // todo: implement + }; + +private: + + // A draw command has everything needed to draw an object + // + // TODO: really, there's no need to keep this state here as data - better + // store it in a common repository of all pipeline states, and index it by + // an ID, which can be compared against, instead of a hash. + GraphicsPipelineState mPipelineState; + +private: /* transient data */ + + uint64_t mPipelineHash = 0; + + // Bindings data for descriptorSets, (vector index == set number) - retrieved from shader on setup + std::vector mDescriptorSetData; + + // Pointer to lookup table for uniform name-> Uniform Key- retrieved from shader on setup + const std::map* mUniformDictionary; + + // Pointer to lookup table for set,binding -> Uniform Key - retrieved from shader on setup + const std::vector>* mUniformBindings; + + // Vector of buffers holding vertex attribute data + std::vector<::vk::Buffer> mVertexBuffers; + + // Offsets into buffer for vertex attribute data + std::vector<::vk::DeviceSize> mVertexOffsets; + + // 1-or-0 element buffer of indices for this draw command + ::vk::Buffer mIndexBuffer = nullptr; + + // Offsets into buffer for index data - this is optional + ::vk::DeviceSize mIndexOffsets = 0; + + // Draw method to be used when submitting DrawCommand + DrawMethod mDrawMethod = DrawMethod::eDraw; + + // draw parameters used when submitting DrawCommand + uint32_t mNumIndices = 0; + uint32_t mNumVertices = 0; + uint32_t mInstanceCount = 1; + uint32_t mFirstVertex = 0; + uint32_t mFirstIndex = 0; + uint32_t mVertexOffset = 0; + uint32_t mFirstInstance = 0; + + std::shared_ptr mMsh; /* optional */ + + // Set data for upload to ubo - data is stored locally + // until draw command is submitted + void commitUniforms( BufferAllocator& alloc_ ); + + void commitMeshAttributes( BufferAllocator& alloc_ ); + +public: + + void setup(const GraphicsPipelineState& pipelineState); + + const GraphicsPipelineState& getPipelineState() const; + + const DescriptorSetData_t& getDescriptorSetData( size_t setId_ ) const; + + const std::vector<::vk::DeviceSize>& getVertexOffsets(); + const std::vector<::vk::Buffer>& getVertexBuffers(); + + const ::vk::DeviceSize& getIndexOffsets(); + const ::vk::Buffer& getIndexBuffer(); + + // Getters and setters for (instanced) draw parameters + DrawCommand& setDrawMethod ( DrawMethod method ); + DrawCommand& setNumIndices ( uint32_t numVertices ); + DrawCommand& setNumVertices ( uint32_t numVertices ); + DrawCommand& setInstanceCount( uint32_t instanceCount ); + DrawCommand& setFirstVertex ( uint32_t firstVertex ); + DrawCommand& setFirstIndex ( uint32_t firstIndex ); + DrawCommand& setVertexOffset ( uint32_t vertexOffset ); + DrawCommand& setFirstInstance( uint32_t firstInstance ); + + DrawMethod getDrawMethod (); + uint32_t getNumIndices (); + uint32_t getNumVertices (); + uint32_t getInstanceCount(); + uint32_t getFirstVertex (); + uint32_t getFirstIndex (); + uint32_t getVertexOffset (); + uint32_t getFirstInstance(); + + // Use ofMesh to draw - this method is here to aid prototyping, and to render dynamic + // meshes. The mesh will get uploaded to temporary GPU memory when the DrawCommand + // is queued up into a RenderBatch. Use setAttribute and setIndices to render static + // meshes, and for more control over how drawing behaves. + of::vk::DrawCommand & setMesh( const std::shared_ptr& msh_ ); + + // Allocate, and store attribute data in gpu memory + template + of::vk::DrawCommand & allocAndSetAttribute( const std::string& attrName_, const std::vector & vec, BufferAllocator& alloc ); + + // Allocate, and store attribute data in gpu memory + template + of::vk::DrawCommand & allocAndSetAttribute( const std::string& attrName_, const T* data, size_t numBytes, BufferAllocator& alloc ); + + // Allocate, and store attribute data in gpu memory + template + of::vk::DrawCommand & allocAndSetAttribute( const size_t& attribLocation_, const std::vector & vec, BufferAllocator& alloc ); + + // Allocate, and store attribute data in gpu memory + of::vk::DrawCommand & allocAndSetAttribute( const size_t& attribLocation_, const void* data, size_t numBytes, BufferAllocator& alloc ); + + // Allocate, and store index data in gpu memory + of::vk::DrawCommand & allocAndSetIndices( const ofIndexType* data, size_t numBytes, BufferAllocator& alloc ); + + + of::vk::DrawCommand & setAttribute( const std::string& name_, ::vk::Buffer buffer_, ::vk::DeviceSize offset_ ); + of::vk::DrawCommand & setAttribute( const size_t attribLocation_, ::vk::Buffer buffer, ::vk::DeviceSize offset ); + of::vk::DrawCommand & setAttribute( const size_t attribLocation_, const of::vk::BufferRegion & bufferRegion_ ); + + of::vk::DrawCommand & setIndices( ::vk::Buffer buffer, ::vk::DeviceSize offset ); + of::vk::DrawCommand & setIndices( const of::vk::BufferRegion& bufferRegion_ ); + + // Store uniform values to staging cpu memory + template + of::vk::DrawCommand & setUniform( const std::string& uniformName, const T& uniformValue_ ); + + // Store ubo to staging cpu memory + template + of::vk::DrawCommand & setUbo( uint32_t setId, uint32_t bindingId, const T& struct_ ); + + of::vk::DrawCommand & setTexture( const std::string& name, const of::vk::Texture& tex_ ); + of::vk::DrawCommand & setStorageBuffer( const std::string& name, const of::vk::BufferRegion& buf_ ); + +}; + +// ------------------------------------------------------------ + +template +inline DrawCommand& DrawCommand::setUbo( uint32_t setId, uint32_t bindingId, const T& struct_ ) { + + if ( mUniformBindings->size() <= setId || (*mUniformBindings)[setId].size() <= bindingId ) { + ofLogWarning() << "Could not find Ubo in set: '" << setId << "' at binding number: " << "'" << bindingId << "' index not found in shader."; + return *this; + } + + // --------| invariant: uniform found + + const auto& uniformInfo = (*mUniformBindings)[setId][bindingId]; + + if ( uniformInfo.dataRange < sizeof( T ) ) { + ofLogWarning() << "Could not set ubo : Data size does not match: " + << " Expected: " << uniformInfo.dataRange << ", received: " << sizeof( T ) << "."; + return *this; + } + + // --------| invariant: size match, we can copy data into our vector. + + auto & dataVec = mDescriptorSetData[uniformInfo.setIndex].dynamicUboData[uniformInfo.auxDataIndex]; + + if ( uniformInfo.dataOffset + uniformInfo.dataRange <= dataVec.size() ) { + memcpy( dataVec.data() + uniformInfo.dataOffset, &struct_, uniformInfo.dataRange ); + } else { + ofLogError() << "Not enough space in local uniform storage. Has this drawCommand been properly initialised?"; + } + + return *this; +} + +// ------------------------------------------------------------ + + +template +inline DrawCommand& DrawCommand::setUniform( const std::string & uniformName, const T & uniformValue_ ){ + + auto uniformInfoIt = mUniformDictionary->find( uniformName ); + + if ( uniformInfoIt == mUniformDictionary->end() ){ + ofLogWarning() << "Could not set Uniform '" << uniformName << "': Uniform name not found in shader"; + return *this; + } + + // --------| invariant: uniform found + + const auto & uniformInfo = uniformInfoIt->second; + + if ( uniformInfo.dataRange < sizeof( T ) ){ + ofLogWarning() << "Could not set uniform '" << uniformName << "': Uniform data size does not match: " + << " Expected: " << uniformInfo.dataRange << ", received: " << sizeof( T ) << "."; + return *this; + } + + // --------| invariant: size match, we can copy data into our vector. + + auto & dataVec = mDescriptorSetData[uniformInfo.setIndex].dynamicUboData[uniformInfo.auxDataIndex]; + + if ( uniformInfo.dataOffset + uniformInfo.dataRange <= dataVec.size() ){ + memcpy( dataVec.data() + uniformInfo.dataOffset, &uniformValue_, uniformInfo.dataRange ); + } else{ + ofLogError() << "Not enough space in local uniform storage. Has this drawCommand been properly initialised?"; + } + + return *this; +} + +// ------------------------------------------------------------ + +inline of::vk::DrawCommand & of::vk::DrawCommand::setTexture( const std::string & uniformName, const of::vk::Texture& tex_ ){ + + auto uniformInfoIt = mUniformDictionary->find( uniformName ); + + if ( uniformInfoIt == mUniformDictionary->end() ){ + ofLogWarning() << "Could not set Texture '" << uniformName << "': Uniform name not found in shader"; + return *this; + } + + // --------| invariant: uniform found + + const auto & uniformInfo = uniformInfoIt->second; + + auto & imageAttachment = mDescriptorSetData[uniformInfo.setIndex].imageAttachment[uniformInfo.auxDataIndex]; + + imageAttachment.sampler = tex_.getSampler(); + imageAttachment.imageView = tex_.getImageView(); + imageAttachment.imageLayout = tex_.getImageLayout(); + + return *this; +} + +// ------------------------------------------------------------ + +inline of::vk::DrawCommand & of::vk::DrawCommand::setStorageBuffer( const std::string & uniformName, const of::vk::BufferRegion& buf_ ){ + + auto uniformInfoIt = mUniformDictionary->find( uniformName ); + + if ( uniformInfoIt == mUniformDictionary->end() ){ + ofLogWarning() << "Could not set Storage Buffer '" << uniformName << "': Uniform name not found in shader"; + return *this; + } + + // --------| invariant: uniform found + + const auto & uniformInfo = uniformInfoIt->second; + + auto & bufferAttachment = mDescriptorSetData[uniformInfo.setIndex].bufferAttachment[uniformInfo.auxDataIndex]; + + bufferAttachment = buf_; + + return *this; +} + +} // namespace +} // end namespace of + +// ------------------------------------------------------------ +// Inline getters and setters + +inline const of::vk::GraphicsPipelineState & of::vk::DrawCommand::getPipelineState() const{ + return mPipelineState; +} + +inline const of::vk::DescriptorSetData_t & of::vk::DrawCommand::getDescriptorSetData( size_t setId_ ) const{ + return mDescriptorSetData[setId_]; +} + +inline const std::vector<::vk::DeviceSize>& of::vk::DrawCommand::getVertexOffsets(){ + return mVertexOffsets; +} + +inline const::vk::DeviceSize & of::vk::DrawCommand::getIndexOffsets(){ + return mIndexOffsets; +} + +inline const std::vector<::vk::Buffer>& of::vk::DrawCommand::getVertexBuffers(){ + return mVertexBuffers; +} + +inline const::vk::Buffer & of::vk::DrawCommand::getIndexBuffer(){ + return mIndexBuffer; +} + +inline uint32_t of::vk::DrawCommand::getNumIndices(){ + return mNumIndices; +} + +inline uint32_t of::vk::DrawCommand::getNumVertices(){ + return mNumVertices; +} + +inline uint32_t of::vk::DrawCommand::getInstanceCount(){ + return mInstanceCount; +} + +inline uint32_t of::vk::DrawCommand::getFirstVertex(){ + return mFirstVertex; +} + +inline uint32_t of::vk::DrawCommand::getFirstIndex(){ + return mFirstIndex; +} + +inline uint32_t of::vk::DrawCommand::getVertexOffset(){ + return mVertexOffset; +} + +inline uint32_t of::vk::DrawCommand::getFirstInstance(){ + return mFirstInstance; +} + +inline of::vk::DrawCommand::DrawMethod of::vk::DrawCommand::getDrawMethod(){ + return mDrawMethod; +} + +inline of::vk::DrawCommand & of::vk::DrawCommand::setNumIndices( uint32_t numIndices ){ + mNumIndices = numIndices; + return *this; +} + +inline of::vk::DrawCommand & of::vk::DrawCommand::setNumVertices( uint32_t numVertices ){ + mNumVertices = numVertices; + return *this; +} + +inline of::vk::DrawCommand & of::vk::DrawCommand::setInstanceCount( uint32_t instanceCount ){ + mInstanceCount = instanceCount; + return *this; +} + +inline of::vk::DrawCommand & of::vk::DrawCommand::setFirstVertex( uint32_t firstVertex ){ + mFirstVertex = firstVertex; + return *this; +} + +inline of::vk::DrawCommand & of::vk::DrawCommand::setFirstIndex( uint32_t firstIndex ){ + mFirstIndex = firstIndex; + return *this; +} + +inline of::vk::DrawCommand & of::vk::DrawCommand::setVertexOffset( uint32_t vertexOffset ){ + mVertexOffset = vertexOffset; + return *this; +} + +inline of::vk::DrawCommand & of::vk::DrawCommand::setFirstInstance( uint32_t firstInstance ){ + mFirstInstance = firstInstance; + return *this; +} + +inline of::vk::DrawCommand & of::vk::DrawCommand::setDrawMethod( DrawMethod method_ ){ + mDrawMethod = method_; + return *this; +} + +// ------------------------------------------------------------ + +inline of::vk::DrawCommand & of::vk::DrawCommand::setAttribute( const size_t attribLocation_, const of::vk::BufferRegion & bufferRegion_ ){ + return setAttribute( attribLocation_, bufferRegion_.buffer, bufferRegion_.offset ); +} + +// ------------------------------------------------------------ + +inline of::vk::DrawCommand & of::vk::DrawCommand::setAttribute( const std::string& name_, ::vk::Buffer buffer_, ::vk::DeviceSize offset_ ){ + size_t index = 0; + if ( mPipelineState.getShader()->getAttributeBinding( name_, index ) ){ + setAttribute( index, buffer_, offset_ ); + return *this; + } + + // --------| invariant: name was not resolved successfully. + + ofLogWarning() + << "Attribute '" << name_ << "' could not be found in shader: " + << mPipelineState.getShader()->getName(); + return *this; +} + +// ------------------------------------------------------------ + +inline of::vk::DrawCommand & of::vk::DrawCommand::setAttribute( const size_t attribLocation_, ::vk::Buffer buffer_, ::vk::DeviceSize offset_ ){ + + if ( attribLocation_ >= mVertexBuffers.size() ){ + ofLogError() << "Attribute location not available: " << attribLocation_; + return *this; + } + + // ---------| invariant: attribLocation is valid + + mVertexBuffers[attribLocation_] = buffer_; + mVertexOffsets[attribLocation_] = offset_; + + return *this; +} + +// ------------------------------------------------------------ + +inline of::vk::DrawCommand & of::vk::DrawCommand::setIndices( const of::vk::BufferRegion& bufferRegion_ ){ + return setIndices( bufferRegion_.buffer, bufferRegion_.offset ); +} + +// ------------------------------------------------------------ + +inline of::vk::DrawCommand & of::vk::DrawCommand::setIndices( ::vk::Buffer buffer_, ::vk::DeviceSize offset_ ){ + mIndexBuffer = buffer_; + mIndexOffsets = offset_; + return *this; +} + +// ------------------------------------------------------------ diff --git a/libs/openFrameworks/vk/HelperTypes.h b/libs/openFrameworks/vk/HelperTypes.h new file mode 100644 index 00000000000..19be333d57b --- /dev/null +++ b/libs/openFrameworks/vk/HelperTypes.h @@ -0,0 +1,240 @@ +#pragma once + +#include +#include "ofLog.h" + +namespace of{ +namespace vk{ + + +struct RendererSettings +{ + uint32_t vkVersion = (1 << 22) | (0 << 12) | (39); // target version + uint32_t numVirtualFrames = 3; // number of virtual frames to allocate and to produce - set this through vkWindowSettings + uint32_t numSwapchainImages = 3; // number of swapchain images to aim for (api gives no guarantee for this.) + ::vk::PresentModeKHR presentMode = ::vk::PresentModeKHR::eFifo; // selected swapchain type (api only guarantees FIFO) + std::vector<::vk::QueueFlags> requestedQueues = { // queues which will be created for this device, index will be queue index in mQueues + ::vk::QueueFlagBits::eGraphics | ::vk::QueueFlagBits::eCompute, + ::vk::QueueFlagBits::eCompute, + ::vk::QueueFlagBits::eTransfer, + }; + bool useDepthStencil = true; + bool useDebugLayers = false; // whether to use vulkan debug layers + + void setVkVersion( int major, int minor, int patch ){ + vkVersion = ( major << 22 ) | ( minor << 12 ) | patch; + } + + int getVkVersionMajor(){ + return ( ( vkVersion >> 22 ) & ( 0x3ff ) ); // 10 bit + } + + int getVersionMinor(){ + return ( ( vkVersion >> 12 ) & ( 0x3ff ) ); // 10 bit + } + + int getVersionPatch(){ + return ( ( vkVersion >> 0 ) & ( 0xfff ) ); + } +}; + +struct RendererProperties +{ + ::vk::Instance instance = nullptr; // vulkan loader instance + ::vk::Device device = nullptr; // virtual device + ::vk::PhysicalDevice physicalDevice = nullptr; // actual GPU + ::vk::PhysicalDeviceProperties physicalDeviceProperties = {}; + ::vk::PhysicalDeviceMemoryProperties physicalDeviceMemoryProperties = {}; + std::vector<::vk::QueueFlags> queueFlags; // << Flags used for requested queue n + std::vector queueFamilyIndices; // << Queue family index for requested queue n + uint32_t graphicsFamilyIndex = ~( uint32_t( 0 ) ); + uint32_t transferFamilyIndex = ~( uint32_t( 0 ) ); + uint32_t computeFamilyIndex = ~( uint32_t( 0 ) ); + uint32_t sparseBindingFamilyIndex = ~( uint32_t( 0 ) ); +}; + +struct TransferSrcData +{ + void * pData ; + ::vk::DeviceSize numElements; + ::vk::DeviceSize numBytesPerElement; +}; + +struct ImageTransferSrcData +{ + void * pData; //< pointer to pixel data + ::vk::DeviceSize numBytes; + ::vk::ImageType imageType { ::vk::ImageType::e2D }; + ::vk::Format format { ::vk::Format::eR8G8B8A8Unorm }; + ::vk::Extent3D extent { 0, 0, 1 }; + uint32_t mipLevels { 1 }; + uint32_t arrayLayers { 1 }; + ::vk::SampleCountFlagBits samples { ::vk::SampleCountFlagBits::e1 }; + + // TODO: Add helper method which adapts ofPixels to ImageTransferSrcData + // + //void setFromOfPixels(const ofPixels& pix ) { + // pData = (void*)pix.getData(); + // numBytes = pix.getTotalBytes(); + // arrayLayers = pix.getNumPlanes(); + // extent + // .setWidth(pix.getWidth()) + // .setHeight(pix.getHeight()) + // ; + // + // auto bpc = pix.getBitsPerChannel(); + // auto pixelFormat = pix.getPixelFormat(); + // switch (pixelFormat) { + // case OF_PIXELS_GRAY: + // if (bpc == 8) + // format = ::vk::Format::eR8Unorm; + // else if (bpc == 16) + // format = ::vk::Format::eR16Sfloat; + // else if (bpc == 32) + // format = ::vk::Format::eR32Sfloat; + // break; + // case OF_PIXELS_GRAY_ALPHA: + // case OF_PIXELS_RGB: + // case OF_PIXELS_BGR: + // case OF_PIXELS_RGBA: + // case OF_PIXELS_BGRA: + // case OF_PIXELS_RGB565: + // } + + //}; +}; + +struct BufferRegion +{ + ::vk::Buffer buffer = nullptr; + ::vk::DeviceSize offset = 0; + ::vk::DeviceSize range = VK_WHOLE_SIZE; + uint64_t numElements = 0; +}; + +// get memory allocation info for best matching memory type that matches any of the type bits and flags +static inline bool getMemoryAllocationInfo( + const ::vk::MemoryRequirements& memReqs, + ::vk::MemoryPropertyFlags memProps, + ::vk::PhysicalDeviceMemoryProperties physicalMemProperties, + ::vk::MemoryAllocateInfo& memInfo ) { + if ( !memReqs.size ){ + memInfo.allocationSize = 0; + memInfo.memoryTypeIndex = ~0u; + return true; + } + + // Find an available memory type that satisfies the requested properties. + uint32_t memoryTypeIndex; + for ( memoryTypeIndex = 0; memoryTypeIndex < physicalMemProperties.memoryTypeCount; ++memoryTypeIndex ){ + if ( ( memReqs.memoryTypeBits & ( 1 << memoryTypeIndex ) ) && + ( physicalMemProperties.memoryTypes[memoryTypeIndex].propertyFlags & memProps ) == memProps ){ + break; + } + } + if ( memoryTypeIndex >= physicalMemProperties.memoryTypeCount ){ + ofLogError() << "memorytypeindex not found" ; + return false; + } + + memInfo.allocationSize = memReqs.size; + memInfo.memoryTypeIndex = memoryTypeIndex; + + return true; +} + +struct DescriptorSetData_t +{ + // Everything a possible descriptor binding might contain. + // Type of decriptor decides which values will be used. + struct DescriptorData_t + { + ::vk::Sampler sampler; // | + ::vk::ImageView imageView; // | > keep in this order, so we can pass address for sampler as descriptorImageInfo + ::vk::ImageLayout imageLayout = ::vk::ImageLayout::eShaderReadOnlyOptimal; // | + ::vk::DescriptorType type = ::vk::DescriptorType::eUniformBufferDynamic; + ::vk::Buffer buffer; // | + ::vk::DeviceSize offset = 0; // | > keep in this order, as we can cast this to a DescriptorBufferInfo + ::vk::DeviceSize range = 0; // | + uint32_t bindingNumber = 0; // <-- may be sparse, may repeat (for arrays of images bound to the same binding), but must increase monotonically (may only repeat or up over the series inside the samplerBindings vector). + uint32_t arrayIndex = 0; // <-- must be in sequence for array elements of same binding + }; + + + // Ordered list of all bindings belonging to this descriptor set + // We use this to calculate a hash of descriptorState. This must + // be tightly packed - that's why we use a vector. + // + // !!!! index is not binding number, as arrayed bindings will be serialized. + std::vector descriptors; + + // Compile-time static assert makes sure DescriptorData can be + // successfully hashed. + static_assert( ( + + sizeof( DescriptorData_t::type ) + + sizeof( DescriptorData_t::sampler ) + + sizeof( DescriptorData_t::imageView ) + + sizeof( DescriptorData_t::imageLayout ) + + sizeof( DescriptorData_t::bindingNumber ) + + sizeof( DescriptorData_t::buffer ) + + sizeof( DescriptorData_t::offset ) + + sizeof( DescriptorData_t::range ) + + sizeof( DescriptorData_t::arrayIndex ) + ) == sizeof( DescriptorData_t ), "DescriptorData_t is not tightly packed. It must be tightly packed for hash calculations." ); + + + std::vector> dynamicUboData; // temp storage for uniform data, one vector of bytes per ubo + std::vector dynamicBindingOffsets; + std::vector<::vk::DescriptorImageInfo> imageAttachment; + std::vector bufferAttachment; +}; + +// ---------- + +struct UniformId_t +{ + /* + + A Uniform Id is a unique key to identify a uniform. + Multiple uniform Ids may point to the same descriptor in case the descriptor is an ubo and has members, for example + + It contains information that tells you where to find corresponding data. + + * setIndex: index into a vector of DescriptorSetData_t for this shader + * descriptorIndex: index into the vector of descriptors of the above DescriptorSetData_t + * auxIndex: index into vector of auxiliary data, which vector is depending on the type of the descriptor returned above + * dataRange: in case the descriptor is an UBO, max size in bytes for the ubo member field + * dataOffset: in case the descriptor is an UBO, offset into the Ubo's memory to get to the first byte owned by the member field + + The shader's mUniformDictionary has a mapping between uniform names and uniform IDs + + + */ + + union + { + // This is currently tightly packed to span 64 bits, but it should be possible to + // make it span 128 bits if necessary. + + uint64_t id = 0; + struct + { + uint64_t setIndex : 3; // 0 .. 7 (maxBoundDescriptorSets is 8) + uint64_t descriptorIndex : 14; // 0 .. 16'383 (index into DescriptorData_t::descriptors, per set) + uint64_t dataOffset : 16; // 0 .. 65'536 (offset within range for ubo members, will always be smaller or equal range) + uint64_t dataRange : 16; // 0 .. 65'536 (max number of bytes per ubo) + uint64_t auxDataIndex : 15; // 0 .. 32'767 (index into helper data vectors per descriptor type per set) + }; + }; + + friend + inline bool operator < ( UniformId_t const & lhs, UniformId_t const & rhs ){ + return lhs.id < rhs.id; + } +}; + +static_assert( sizeof( UniformId_t ) == sizeof( uint64_t ), "UniformId_t is not proper size." ); + + +} // end namespace of::vk +} // end namespace of diff --git a/libs/openFrameworks/vk/ImageAllocator.cpp b/libs/openFrameworks/vk/ImageAllocator.cpp new file mode 100644 index 00000000000..e75772d3079 --- /dev/null +++ b/libs/openFrameworks/vk/ImageAllocator.cpp @@ -0,0 +1,102 @@ +#include "vk/ImageAllocator.h" +#include "ofLog.h" + +using namespace std; +using namespace of::vk; + +// ---------------------------------------------------------------------- + +void ImageAllocator::setup(const ImageAllocator::Settings& settings){ + + const_cast(mSettings) = settings; + + const_cast<::vk::DeviceSize&>( mImageGranularity ) = mSettings.physicalDeviceProperties.limits.bufferImageGranularity; + + // make sure reserved memory is multiple of alignment (= ImageGranularity) + const_cast<::vk::DeviceSize&>( mSettings.size ) = mImageGranularity * ( ( mSettings.size + mImageGranularity - 1 ) / mImageGranularity ); + + ::vk::MemoryRequirements memReqs; + { + ::vk::ImageCreateInfo imageCreateInfo; + ::vk::ImageFormatProperties imageFormatProperties; + ::vk::Format format = ::vk::Format::eR8G8B8A8Unorm; + + imageCreateInfo + .setImageType( ::vk::ImageType::e2D ) + .setFormat( format ) + .setExtent( { 1, 1, 1 } ) + .setMipLevels( 1 ) + .setArrayLayers( 1 ) + .setSamples( ::vk::SampleCountFlagBits::e1 ) + .setTiling( mSettings.imageTiling ) + .setUsage( mSettings.imageUsageFlags ) + .setSharingMode( ::vk::SharingMode::eExclusive ) + .setQueueFamilyIndexCount( mSettings.queueFamilyIndices.size() ) + .setPQueueFamilyIndices( mSettings.queueFamilyIndices.data() ) + .setInitialLayout( ::vk::ImageLayout::eUndefined ) + ; + + ::vk::Image tmpImage = mSettings.device.createImage( imageCreateInfo ); + + // Get memory requirements - we're really just interested in memory type bits + memReqs = mSettings.device.getImageMemoryRequirements( tmpImage ); + + mSettings.device.destroyImage( tmpImage ); + } + + memReqs.size = mSettings.size; + memReqs.alignment = mImageGranularity; + + ::vk::MemoryAllocateInfo allocateInfo; + + bool result = getMemoryAllocationInfo( + mSettings.physicalDeviceMemoryProperties, + memReqs, + mSettings.memFlags, + allocateInfo + ); + + mDeviceMemory = mSettings.device.allocateMemory( allocateInfo ); + + mOffsetEnd = 0; +} + +// ---------------------------------------------------------------------- + +void of::vk::ImageAllocator::reset(){ + if ( mDeviceMemory ){ + mSettings.device.freeMemory( mDeviceMemory ); + mDeviceMemory = nullptr; + } + + mOffsetEnd = 0; +} + +// ---------------------------------------------------------------------- +// brief linear allocator +// param byteCount number of bytes to allocate +// out param offset : address of first byte of allocated image +bool ImageAllocator::allocate( ::vk::DeviceSize byteCount_, ::vk::DeviceSize& offset ){ + uint32_t alignedByteCount = mImageGranularity * ( ( byteCount_ + mImageGranularity - 1 ) / mImageGranularity ); + + if ( mOffsetEnd + alignedByteCount <= (mSettings.size) ){ + // write out offset + offset = mOffsetEnd; + mOffsetEnd += alignedByteCount; + return true; + } else{ + ofLogError() << "Image Allocator: out of memory"; + } + return false; +} + +// ---------------------------------------------------------------------- + +void ImageAllocator::free(){ + mOffsetEnd = 0; +} + +// ---------------------------------------------------------------------- + +void ImageAllocator::swap(){ +} diff --git a/libs/openFrameworks/vk/ImageAllocator.h b/libs/openFrameworks/vk/ImageAllocator.h new file mode 100644 index 00000000000..e65bf386fe4 --- /dev/null +++ b/libs/openFrameworks/vk/ImageAllocator.h @@ -0,0 +1,125 @@ +#pragma once + +#include "vk/Allocator.h" +#include "vk/HelperTypes.h" + +namespace of{ +namespace vk{ + +// ---------------------------------------------------------------------- + + +/* + BufferAllocator is a simple linear allocator. + + Allocator may have more then one virtual frame, + and only allocations from the current virutal + frame are performed until swap(). + + Allocator may be for transient memory or for + static memory. + + If allocated from Host memory, the allocator + maps a buffer to CPU visible memory for its + whole lifetime. + +*/ + + +class ImageAllocator : public AbstractAllocator +{ + +public: + + struct Settings : public AbstractAllocator::Settings + { + ::vk::ImageUsageFlags imageUsageFlags = ( + ::vk::ImageUsageFlagBits::eTransferDst + | ::vk::ImageUsageFlagBits::eSampled + ); + + ::vk::ImageTiling imageTiling = ::vk::ImageTiling::eOptimal; + + // ----- convenience methods + + Settings & setSize( ::vk::DeviceSize size_ ){ + AbstractAllocator::Settings::size = size_; + return *this; + } + Settings & setMemFlags( ::vk::MemoryPropertyFlags flags_ ){ + AbstractAllocator::Settings::memFlags = flags_; + return *this; + } + Settings & setQueueFamilyIndices( const std::vector indices_ ){ + AbstractAllocator::Settings::queueFamilyIndices = indices_; + return *this; + } + Settings & setRendererProperties( const of::vk::RendererProperties& props ){ + AbstractAllocator::Settings::device = props.device; + AbstractAllocator::Settings::physicalDeviceMemoryProperties = props.physicalDeviceMemoryProperties; + AbstractAllocator::Settings::physicalDeviceProperties = props.physicalDeviceProperties; + return *this; + } + Settings & setImageUsageFlags( const ::vk::ImageUsageFlags& flags_ ){ + imageUsageFlags = flags_; + return *this; + } + Settings & setImageTiling( const ::vk::ImageTiling & tiling_ ){ + imageTiling = tiling_; + return *this; + } + }; + + ImageAllocator( ) + : mSettings(){}; + + ~ImageAllocator(){ + mSettings.device.waitIdle(); + reset(); + }; + + /// @detail set up allocator based on Settings and pre-allocate + /// a chunk of GPU memory, and attach a buffer to it + void setup(const ImageAllocator::Settings& settings) ; + + /// @brief free GPU memory and de-initialise allocator + void reset() override; + + /// @brief sub-allocate a chunk of memory from GPU + /// + bool allocate( ::vk::DeviceSize byteCount_, ::vk::DeviceSize& offset ) override; + + void swap() override; + + const ::vk::DeviceMemory& getDeviceMemory() const override; + + /// @brief remove all sub-allocations within the given frame + /// @note this does not free GPU memory, it just marks it as unused + void free(); + + // jump to use next segment assigned to next virtual frame + + const AbstractAllocator::Settings& getSettings() const override{ + return mSettings; + } + +private: + const ImageAllocator::Settings mSettings; + const ::vk::DeviceSize mImageGranularity = (1UL << 10); // granularity is calculated on setup. must be power of two. + + ::vk::DeviceSize mOffsetEnd = 0; // next free location for allocations + ::vk::DeviceMemory mDeviceMemory = nullptr; // owning + +}; + +// ---------------------------------------------------------------------- + +inline const ::vk::DeviceMemory & of::vk::ImageAllocator::getDeviceMemory() const{ + return mDeviceMemory; +} + +// ---------------------------------------------------------------------- + + +} // namespace of::vk +} // namespace of \ No newline at end of file diff --git a/libs/openFrameworks/vk/ImgSwapchain.cpp b/libs/openFrameworks/vk/ImgSwapchain.cpp new file mode 100644 index 00000000000..1da77e0353d --- /dev/null +++ b/libs/openFrameworks/vk/ImgSwapchain.cpp @@ -0,0 +1,404 @@ +#include "vk/ImgSwapchain.h" +#include "vk/ImageAllocator.h" +#include "vk/BufferAllocator.h" +#include "vk/ofVkRenderer.h" +#include "ofImage.h" +#include "ofPixels.h" + +using namespace std; +using namespace of::vk; + +// ---------------------------------------------------------------------- + +ImgSwapchain::ImgSwapchain( const ImgSwapchainSettings & settings_ ) + : mSettings( settings_ ){} + +// ---------------------------------------------------------------------- + +void ImgSwapchain::setRendererProperties( const of::vk::RendererProperties & rendererProperties_ ){ + mRendererProperties = rendererProperties_; +} + +// ---------------------------------------------------------------------- + +void ImgSwapchain::setup(){ + + // create image allocator + + ImageAllocator::Settings imageAllocatorSettings; + imageAllocatorSettings.imageUsageFlags = ::vk::ImageUsageFlagBits::eColorAttachment | ::vk::ImageUsageFlagBits::eTransferSrc; + imageAllocatorSettings.imageTiling = ::vk::ImageTiling::eOptimal; + imageAllocatorSettings.physicalDeviceMemoryProperties = mRendererProperties.physicalDeviceMemoryProperties; + imageAllocatorSettings.physicalDeviceProperties = mRendererProperties.physicalDeviceProperties; + imageAllocatorSettings.device = mRendererProperties.device; + imageAllocatorSettings.memFlags = ::vk::MemoryPropertyFlagBits::eDeviceLocal; + // We add one buffer Image granularity per frame for good measure, + // so we can be sure we won't run out of memory because our images need padding + imageAllocatorSettings.size = ( mSettings.width * mSettings.height * 4 + mRendererProperties.physicalDeviceProperties.limits.bufferImageGranularity ) + * mSettings.numSwapchainImages; + + mImageAllocator.setup(imageAllocatorSettings); + + // create buffer allocator + + BufferAllocator::Settings bufferAllocatorSettings; + bufferAllocatorSettings.physicalDeviceMemoryProperties = mRendererProperties.physicalDeviceMemoryProperties; + bufferAllocatorSettings.physicalDeviceProperties = mRendererProperties.physicalDeviceProperties; + bufferAllocatorSettings.frameCount = mSettings.numSwapchainImages; + bufferAllocatorSettings.device = mRendererProperties.device; + bufferAllocatorSettings.memFlags = ::vk::MemoryPropertyFlagBits::eHostVisible | ::vk::MemoryPropertyFlagBits::eHostCoherent; + bufferAllocatorSettings.size = mSettings.width * mSettings.height * 4 * mSettings.numSwapchainImages; + bufferAllocatorSettings.bufferUsageFlags = ::vk::BufferUsageFlagBits::eTransferDst | ::vk::BufferUsageFlagBits::eTransferSrc; + + mBufferAllocator.setup(bufferAllocatorSettings); + + // Create command pool for internal command buffers. + { + ::vk::CommandPoolCreateInfo createInfo; + createInfo + .setFlags( ::vk::CommandPoolCreateFlagBits::eResetCommandBuffer ) + .setQueueFamilyIndex( mRendererProperties.graphicsFamilyIndex ) //< Todo: make sure this has been set properly when renderer/queues were set up. + ; + mCommandPool = mDevice.createCommandPool( createInfo ); + } + + // Create transfer frames. + mTransferFrames.resize( mImageCount ); + + for ( size_t i = 0; i != mImageCount; ++i ){ + + ::vk::ImageCreateInfo createInfo; + createInfo + .setImageType( ::vk::ImageType::e2D ) + .setFormat( mSettings.colorFormat ) + .setExtent( { mSettings.width, mSettings.height, 1 } ) + .setMipLevels( 1 ) + .setArrayLayers( 1 ) + .setSamples( ::vk::SampleCountFlagBits::e1 ) + .setTiling( ::vk::ImageTiling::eOptimal ) + .setUsage( ::vk::ImageUsageFlagBits::eColorAttachment | ::vk::ImageUsageFlagBits::eTransferSrc ) + .setSharingMode( ::vk::SharingMode::eExclusive ) + .setQueueFamilyIndexCount( 1 ) + .setPQueueFamilyIndices( &mRendererProperties.graphicsFamilyIndex ) + .setInitialLayout( ::vk::ImageLayout::eUndefined ) + ; + + auto & img = mTransferFrames[i].image.imageRef = mDevice.createImage( createInfo ); + + // Allocate image memory via image allocator + { + ::vk::DeviceSize offset = 0; + mImageAllocator.allocate( mSettings.width * mSettings.height * 4, offset ); + mDevice.bindImageMemory( img, mImageAllocator.getDeviceMemory(), offset ); + } + + ::vk::ImageSubresourceRange subresourceRange; + subresourceRange + .setAspectMask( ::vk::ImageAspectFlags( ::vk::ImageAspectFlagBits::eColor ) ) + .setBaseMipLevel( 0 ) + .setLevelCount( 1 ) + .setBaseArrayLayer( 0 ) + .setLayerCount( 1 ) + ; + + ::vk::ImageViewCreateInfo imageViewCreateInfo; + imageViewCreateInfo + .setImage( img ) + .setViewType( ::vk::ImageViewType::e2D ) + .setFormat( mSettings.colorFormat ) + .setSubresourceRange( subresourceRange ) + ; + + mTransferFrames[i].image.view = mDevice.createImageView( imageViewCreateInfo ); + + // allocate host-visible buffer memory, and map buffer memory + { + ::vk::DeviceSize offset = 0; + mBufferAllocator.allocate( mSettings.width * mSettings.height * 4, offset ); + mTransferFrames[i].bufferRegion.buffer = mBufferAllocator.getBuffer(); + mTransferFrames[i].bufferRegion.offset = offset; + mTransferFrames[i].bufferRegion.range = mSettings.width * mSettings.height * 4; + + // map the host-visible ram address for the buffer to the current frame + // so information can be read back. + mBufferAllocator.map( mTransferFrames[i].bufferReadAddress ); + // we swap the allocator since we use one frame per id + // and swap tells the allocator to go to the next virtual frame + mBufferAllocator.swap(); + } + + mTransferFrames[i].frameFence = mDevice.createFence( { ::vk::FenceCreateFlagBits::eSignaled } ); + + } + + { + ::vk::CommandBufferAllocateInfo allocateInfo; + allocateInfo + .setCommandPool( mCommandPool ) + .setLevel( ::vk::CommandBufferLevel::ePrimary) + .setCommandBufferCount( mTransferFrames.size() * 2 ) + ; + + auto cmdBuffers = mDevice.allocateCommandBuffers( allocateInfo ); + + // Todo: fill in commands. + + for ( size_t i = 0; i != mTransferFrames.size(); ++i ){ + mTransferFrames[i].cmdAcquire = cmdBuffers[i*2]; + mTransferFrames[i].cmdPresent = cmdBuffers[i*2+1]; + } + } + + for (size_t i = 0; i!=mTransferFrames.size(); ++i ){ + { + // copy == transfer image to buffer memory + ::vk::CommandBuffer & cmd = mTransferFrames[i].cmdPresent; + + cmd.begin( { ::vk::CommandBufferUsageFlags() } ); + + ::vk::ImageMemoryBarrier imgMemBarrier; + imgMemBarrier + .setSrcAccessMask( ::vk::AccessFlagBits::eMemoryRead ) + .setDstAccessMask( ::vk::AccessFlagBits::eTransferRead ) + .setOldLayout( ::vk::ImageLayout::ePresentSrcKHR ) + .setNewLayout( ::vk::ImageLayout::eTransferSrcOptimal ) + .setSrcQueueFamilyIndex( mRendererProperties.graphicsFamilyIndex ) // < TODO: queue ownership: graphics -> transfer + .setDstQueueFamilyIndex( mRendererProperties.graphicsFamilyIndex ) // < TODO: queue ownership: graphics -> transfer + .setImage( mTransferFrames[i].image.imageRef ) + .setSubresourceRange( { ::vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } ) + ; + + cmd.pipelineBarrier( ::vk::PipelineStageFlagBits::eAllCommands, ::vk::PipelineStageFlagBits::eTransfer, ::vk::DependencyFlags(), {}, {}, { imgMemBarrier } ); + + ::vk::ImageSubresourceLayers imgSubResource; + imgSubResource + .setAspectMask( ::vk::ImageAspectFlagBits::eColor ) + .setMipLevel( 0 ) + .setBaseArrayLayer( 0 ) + .setLayerCount( 1 ) + ; + + ::vk::BufferImageCopy imgCopy; + imgCopy + .setBufferOffset( mTransferFrames[i].bufferRegion.offset ) + .setBufferRowLength( mSettings.width ) + .setBufferImageHeight( mSettings.height ) + .setImageSubresource( imgSubResource ) + .setImageOffset( { 0 } ) + .setImageExtent( { mSettings.width, mSettings.height, 1 } ) + ; + + // image must be transferred to a buffer - we can then read from this buffer. + cmd.copyImageToBuffer( mTransferFrames[i].image.imageRef, ::vk::ImageLayout::eTransferSrcOptimal, mTransferFrames[i].bufferRegion.buffer, { imgCopy } ); + cmd.end(); + } + + { + // Move ownership of image back from transfer -> graphics + // Change image layout back to colorattachment + + ::vk::CommandBuffer & cmd = mTransferFrames[i].cmdAcquire; + + cmd.begin( {::vk::CommandBufferUsageFlags()} ); + + ::vk::ImageMemoryBarrier imgMemBarrier; + imgMemBarrier + .setSrcAccessMask( ::vk::AccessFlagBits::eTransferRead ) + .setDstAccessMask( ::vk::AccessFlagBits::eColorAttachmentWrite ) + .setOldLayout( ::vk::ImageLayout::eUndefined ) + .setNewLayout( ::vk::ImageLayout::eColorAttachmentOptimal ) + .setSrcQueueFamilyIndex( mRendererProperties.graphicsFamilyIndex ) // < TODO: queue ownership: transfer -> graphics + .setDstQueueFamilyIndex( mRendererProperties.graphicsFamilyIndex ) // < TODO: queue ownership: transfer -> graphics + .setImage( mTransferFrames[i].image.imageRef ) + .setSubresourceRange( { ::vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } ) + ; + + cmd.pipelineBarrier( ::vk::PipelineStageFlagBits::eAllCommands, ::vk::PipelineStageFlagBits::eTransfer, ::vk::DependencyFlags(), {}, {}, { imgMemBarrier } ); + + cmd.end(); + + } + + } + + + // Pre-set imageIndex so it will start at 0 with first increment. + mImageIndex = mImageCount - 1; + +} + +// ---------------------------------------------------------------------- + +ImgSwapchain::~ImgSwapchain(){ + + for ( auto & f : mTransferFrames ){ + if ( f.image.imageRef){ + mDevice.destroyImageView( f.image.view ); + mDevice.destroyImage( f.image.imageRef ); + } + if ( f.frameFence ){ + mDevice.destroyFence( f.frameFence ); + } + } + + mDevice.destroyCommandPool( mCommandPool ); + + mTransferFrames.clear(); + +} + +// ---------------------------------------------------------------------- + +// immediately return index of next available index, +// defer signalling of semaphore until this image is ready for write +::vk::Result ImgSwapchain::acquireNextImage( ::vk::Semaphore presentCompleteSemaphore, uint32_t & imageIndex ){ + + // What this method does: it gets the next available (free to render into) image from the + // internal queue of images, and returns its index, effectively passing ownership of this + // image to the renderer. + + // This method must signal the semaphore `presentCompleteSemaphore` + // as soon as the image is free to be rendered into. + + imageIndex = ( mImageIndex + 1 ) % mImageCount; + + auto fenceWaitResult = mDevice.waitForFences( { mTransferFrames[imageIndex].frameFence }, VK_TRUE, 100'000'000 ); + + if ( fenceWaitResult != ::vk::Result::eSuccess ){ + ofLogError() << "ImgSwapchain: Waiting for fence takes too long: " << ::vk::to_string( fenceWaitResult ); + } + + mDevice.resetFences( { mTransferFrames[imageIndex].frameFence } ); + + mImageIndex = imageIndex; + + + static ofPixels mPixels; + + if ( false ){ + // memcpy into pixels object + if ( !mPixels.isAllocated() ){ + mPixels.allocate( mSettings.width, mSettings.height, ofImageType::OF_IMAGE_COLOR_ALPHA ); + ofLogNotice() << "Image Swapchain: Allocating pixels."; + } + memcpy( mPixels.getData(), mTransferFrames[imageIndex].bufferReadAddress, mPixels.size() ); + } else { + // Directly use ofPixels object to wrap memory + mPixels.setFromExternalPixels( reinterpret_cast( mTransferFrames[imageIndex].bufferReadAddress ), + size_t( mSettings.width ), size_t( mSettings.height ), ofPixelFormat::OF_PIXELS_RGBA ); + } + + // we could use an event here to synchronise host <-> device, meaning + // a command buffer on the device would wait execution until the event signalling that the + // copy operation has completed was signalled by the host. + + std::array numStr; + sprintf( numStr.data(), "%08zd.png", mImageCounter ); + std::string filename( numStr.data(), numStr.size() ); + + // Invariant: we can assume the image has been transferred into the mapped buffer. + // Now we must write the memory from the mapped buffer to the hard drive. + ofSaveImage( mPixels, ( mSettings.path + filename ), ofImageQualityType::OF_IMAGE_QUALITY_BEST ); + + ++mImageCounter; + + // The number of array elements must correspond to the number of wait semaphores, as each + // mask specifies what the semaphore is waiting for. + std::array<::vk::PipelineStageFlags, 1> wait_dst_stage_mask = { ::vk::PipelineStageFlagBits::eTransfer }; + + ::vk::SubmitInfo submitInfo; + submitInfo + .setWaitSemaphoreCount( 0 ) + .setPWaitSemaphores( nullptr ) + .setPWaitDstStageMask( nullptr ) + .setCommandBufferCount( 1 ) + .setPCommandBuffers( &mTransferFrames[imageIndex].cmdAcquire ) + .setSignalSemaphoreCount( 1 ) + .setPSignalSemaphores( &presentCompleteSemaphore ) + ; + + // !TODO: instead of submitting to queue 0, this needs to go to the transfer queue. + mSettings.renderer->submit( 0, { submitInfo }, nullptr ); + + return ::vk::Result(); +} + +// ---------------------------------------------------------------------- + +::vk::Result ImgSwapchain::queuePresent( ::vk::Queue queue, std::mutex & queueMutex, const std::vector<::vk::Semaphore>& waitSemaphores_ ){ + + /* + + Submit the transfer command buffer - set waitSemaphores, so that the transfer command buffer will only execute once + the semaphore has been signalled. + + We use the transfer fence to make sure that the command buffer has finished executing. + + Once that's done, we issue another command buffer which will transfer the image back to its preferred layout. + + */ + + ::vk::PipelineStageFlags wait_dst_stage_mask = ::vk::PipelineStageFlagBits::eColorAttachmentOutput; + + ::vk::SubmitInfo submitInfo; + submitInfo + .setWaitSemaphoreCount( waitSemaphores_.size() ) + .setPWaitSemaphores( waitSemaphores_.data()) // these are the renderComplete semaphores + .setPWaitDstStageMask( &wait_dst_stage_mask ) + .setCommandBufferCount( 1 ) // TODO: set transfer command buffer here. + .setPCommandBuffers( &mTransferFrames[mImageIndex].cmdPresent ) // TODO: set transfer command buffer here. + .setSignalSemaphoreCount( 0 ) + .setPSignalSemaphores( nullptr ) // once this has been reached, the semaphore for present complete will signal. + ; + + // Todo: submit to transfer queue, not main queue, if possible + + { + std::lock_guard lock{ queueMutex }; + queue.submit( { submitInfo }, mTransferFrames[mImageIndex].frameFence ); + } + + return ::vk::Result(); +} + +// ---------------------------------------------------------------------- + +const ImageWithView & ImgSwapchain::getImage( size_t i ) const{ + return mTransferFrames[i].image; +} + +// ---------------------------------------------------------------------- + +const uint32_t ImgSwapchain::getImageCount() const{ + return mTransferFrames.size(); +} + +// ---------------------------------------------------------------------- + +const uint32_t & ImgSwapchain::getCurrentImageIndex() const{ + return mImageIndex; +} + +// ---------------------------------------------------------------------- + +const ::vk::Format & ImgSwapchain::getColorFormat(){ + return mSettings.colorFormat; +} + +// ---------------------------------------------------------------------- +uint32_t ImgSwapchain::getWidth(){ + return mSettings.width; +} + +// ---------------------------------------------------------------------- +uint32_t ImgSwapchain::getHeight(){ + return mSettings.height; +} + +// ---------------------------------------------------------------------- +void ImgSwapchain::changeExtent( uint32_t w, uint32_t h ){ + const_cast( mSettings.width ) = w; + const_cast( mSettings.height ) = h; +} + +// ---------------------------------------------------------------------- \ No newline at end of file diff --git a/libs/openFrameworks/vk/ImgSwapchain.h b/libs/openFrameworks/vk/ImgSwapchain.h new file mode 100644 index 00000000000..a459cb74551 --- /dev/null +++ b/libs/openFrameworks/vk/ImgSwapchain.h @@ -0,0 +1,98 @@ +#pragma once + +#include "vk/Swapchain.h" +#include "vk/BufferAllocator.h" +#include "vk/ImageAllocator.h" + +class ofVkRenderer; //ffdecl + +namespace of{ +namespace vk{ + +// ---------------------------------------------------------------------- + +struct ImgSwapchainSettings : public SwapchainSettings +{ + std::string path = "render/img_"; + ::vk::Format colorFormat = ::vk::Format::eR8G8B8A8Unorm; + std::shared_ptr<::ofVkRenderer> renderer; +}; + +// ---------------------------------------------------------------------- + +class ImgSwapchain : public Swapchain +{ + const ImgSwapchainSettings mSettings; + + const uint32_t &mImageCount = mSettings.numSwapchainImages; + uint32_t mImageIndex = 0; + + ImageAllocator mImageAllocator; + BufferAllocator mBufferAllocator; + + struct TransferFrame + { + ImageWithView image; + BufferRegion bufferRegion; + void* bufferReadAddress; // mapped address for host visible buffer memory + ::vk::Fence frameFence; + ::vk::CommandBuffer cmdPresent; + ::vk::CommandBuffer cmdAcquire; + }; + + ::vk::CommandPool mCommandPool; //< command pool for local command buffers + + std::vector mTransferFrames; + + RendererProperties mRendererProperties; + const ::vk::Device &mDevice = mRendererProperties.device; + + ::vk::Queue mTransferQueue = nullptr; + + size_t mImageCounter = 0; // running image count + +public: + + ImgSwapchain( const ImgSwapchainSettings& settings_ ); + + void setRendererProperties( const of::vk::RendererProperties& rendererProperties_ ) override; + + void setup() override; + + virtual ~ImgSwapchain(); + + // Request an image index from the swapchain, so that we might render into it + // the image must be returned to the swapchain when done using queuePresent + // \note this might cause waiting. + ::vk::Result acquireNextImage( ::vk::Semaphore presentCompleteSemaphore, uint32_t &imageIndex ) override; + + + // Present the current image to the queue + // Waits with execution until all waitSemaphores have been signalled + ::vk::Result queuePresent( ::vk::Queue queue, std::mutex & queueMutex, const std::vector<::vk::Semaphore>& waitSemaphores_ ) override; + + // return image by index + const ImageWithView& getImage( size_t i ) const override; + + // return number of swapchain images + const uint32_t getImageCount() const override; + + // return last acquired buffer id + const uint32_t & getCurrentImageIndex() const override; + + const ::vk::Format& getColorFormat() override; + + // Return current swapchain image width in pixels + uint32_t getWidth() override; + + // Return current swapchain image height in pixels + uint32_t getHeight() override; + + // Change width and height in internal settings. + // Caution: this method requires a call to setup() to be applied, and is very costly. + void changeExtent( uint32_t w, uint32_t h ) override; + +}; + +} // end namespace vk +} // end namespace of \ No newline at end of file diff --git a/libs/openFrameworks/vk/Pipeline.cpp b/libs/openFrameworks/vk/Pipeline.cpp new file mode 100644 index 00000000000..9290b83cf26 --- /dev/null +++ b/libs/openFrameworks/vk/Pipeline.cpp @@ -0,0 +1,393 @@ +#include "vk/Pipeline.h" +#include "vk/Shader.h" +#include "spooky/SpookyV2.h" +#include + +using namespace std; +using namespace of::vk; + +// ---------------------------------------------------------------------- + +::vk::Pipeline ComputePipelineState::createPipeline( const ::vk::Device & device, const std::shared_ptr<::vk::PipelineCache> & pipelineCache, ::vk::Pipeline basePipelineHandle_ ){ + ::vk::Pipeline pipeline; + + ::vk::PipelineCreateFlags createFlags; + + if ( basePipelineHandle_ ){ + // if we already have a base pipeline handle, + // this means we want to create the next pipeline as + // a derivative of the previous pipeline. + createFlags |= ::vk::PipelineCreateFlagBits::eDerivative; + } else{ + // if we have got no base pipeline handle, + // we want to signal that this pipeline is not derived from any other, + // but may allow derivative pipelines. + createFlags |= ::vk::PipelineCreateFlagBits::eAllowDerivatives; + } + + ::vk::ComputePipelineCreateInfo createInfo; + createInfo + .setFlags( createFlags ) + .setStage( mShader->getShaderStageCreateInfo().front() ) + .setLayout( *mShader->getPipelineLayout() ) + .setBasePipelineHandle( basePipelineHandle_ ) + .setBasePipelineIndex( mBasePipelineIndex ) + ; + + pipeline = device.createComputePipeline( *pipelineCache, createInfo, nullptr ); + + mDirty = false; + + return pipeline; +} + +// ---------------------------------------------------------------------- + +void ComputePipelineState::setShader( const std::shared_ptr& shader ){ + if ( shader.get() != mShader.get() ){ + mShader = shader; + mDirty = true; + } +} + +// ---------------------------------------------------------------------- + +void ComputePipelineState::touchShader() const{ + mDirty = mShader->compile(); +} + +// ---------------------------------------------------------------------- + +bool ComputePipelineState::operator==( ComputePipelineState const & rhs ){ + return mShader->getShaderCodeHash() == rhs.mShader->getShaderCodeHash(); +} + +// ---------------------------------------------------------------------- + +uint64_t ComputePipelineState::calculateHash() const{ + + std::vector setLayoutKeys = mShader->getDescriptorSetLayoutKeys(); + + uint64_t hash = mShader->getShaderCodeHash(); + + hash = SpookyHash::Hash64( setLayoutKeys.data(), sizeof( uint64_t ) * setLayoutKeys.size(), hash ); + + return hash; +} + +// ---------------------------------------------------------------------- + +GraphicsPipelineState::GraphicsPipelineState(){ + reset(); +} + +// ---------------------------------------------------------------------- + +void GraphicsPipelineState::reset() +{ + inputAssemblyState = ::vk::PipelineInputAssemblyStateCreateInfo(); + inputAssemblyState + .setTopology( ::vk::PrimitiveTopology::eTriangleList ) + .setPrimitiveRestartEnable( VK_FALSE ) + ; + + tessellationState = ::vk::PipelineTessellationStateCreateInfo(); + tessellationState + .setPatchControlPoints( 3 ) + ; + + // viewport and scissor are tracked as dynamic states, so this object + // will not get used. + viewportState = ::vk::PipelineViewportStateCreateInfo(); + viewportState + .setViewportCount( 1 ) + .setPViewports( nullptr ) + .setScissorCount( 1 ) + .setPScissors( nullptr ) + ; + + rasterizationState = ::vk::PipelineRasterizationStateCreateInfo(); + rasterizationState + .setDepthClampEnable( VK_FALSE ) + .setRasterizerDiscardEnable( VK_FALSE ) + .setPolygonMode( ::vk::PolygonMode::eFill ) + .setCullMode( ::vk::CullModeFlagBits::eBack ) + .setFrontFace( ::vk::FrontFace::eCounterClockwise ) + .setDepthBiasEnable( VK_FALSE ) + .setDepthBiasConstantFactor( 0.f ) + .setDepthBiasClamp( 0.f ) + .setDepthBiasSlopeFactor( 1.f ) + .setLineWidth( 1.f ) + ; + + multisampleState = ::vk::PipelineMultisampleStateCreateInfo(); + multisampleState + .setRasterizationSamples( ::vk::SampleCountFlagBits::e1 ) + .setSampleShadingEnable( VK_FALSE ) + .setMinSampleShading( 0.f ) + .setPSampleMask( nullptr ) + .setAlphaToCoverageEnable( VK_FALSE ) + .setAlphaToOneEnable( VK_FALSE ) + ; + + ::vk::StencilOpState stencilOpState; + stencilOpState + .setFailOp( ::vk::StencilOp::eKeep ) + .setPassOp( ::vk::StencilOp::eKeep ) + .setDepthFailOp( ::vk::StencilOp::eKeep ) + .setCompareOp( ::vk::CompareOp::eNever ) + .setCompareMask( 0 ) + .setWriteMask( 0 ) + .setReference( 0 ) + ; + + depthStencilState = ::vk::PipelineDepthStencilStateCreateInfo(); + depthStencilState + .setDepthTestEnable( VK_TRUE ) + .setDepthWriteEnable( VK_TRUE) + .setDepthCompareOp( ::vk::CompareOp::eLessOrEqual ) + .setDepthBoundsTestEnable( VK_FALSE ) + .setStencilTestEnable( VK_FALSE ) + .setFront( stencilOpState ) + .setBack( stencilOpState ) + .setMinDepthBounds( 0.f ) + .setMaxDepthBounds( 0.f ) + ; + + blendAttachmentStates.fill( ::vk::PipelineColorBlendAttachmentState() ); + + blendAttachmentStates[0] + .setBlendEnable( VK_FALSE ) + .setColorBlendOp( ::vk::BlendOp::eAdd) + .setAlphaBlendOp( ::vk::BlendOp::eAdd) + .setSrcColorBlendFactor( ::vk::BlendFactor::eOne) // eOne, because we require premultiplied alpha! + .setDstColorBlendFactor( ::vk::BlendFactor::eOneMinusSrcAlpha ) + .setSrcAlphaBlendFactor( ::vk::BlendFactor::eOne) + .setDstAlphaBlendFactor( ::vk::BlendFactor::eZero ) + .setColorWriteMask( + ::vk::ColorComponentFlagBits::eR | + ::vk::ColorComponentFlagBits::eG | + ::vk::ColorComponentFlagBits::eB | + ::vk::ColorComponentFlagBits::eA + ) + ; + + colorBlendState = ::vk::PipelineColorBlendStateCreateInfo(); + colorBlendState + .setLogicOpEnable( VK_FALSE ) + .setLogicOp( ::vk::LogicOp::eClear ) + .setAttachmentCount( 1 ) + .setPAttachments ( nullptr ) + .setBlendConstants( {0.f, 0.f, 0.f, 0.f} ) + ; + + dynamicStates = { + ::vk::DynamicState::eScissor, + ::vk::DynamicState::eViewport, + }; + + dynamicState = ::vk::PipelineDynamicStateCreateInfo(); + dynamicState + .setDynamicStateCount( dynamicStates.size() ) + .setPDynamicStates( nullptr ) + ; + + mRenderPass = nullptr; + mSubpass = 0; + mBasePipelineIndex = -1; + + mShader.reset(); +} + +// ---------------------------------------------------------------------- +void GraphicsPipelineState::setBlendMode( uint8_t attachmentIdx, GraphicsPipelineState::BlendMode mode){ + if ( attachmentIdx >= blendAttachmentStates.size() ) { + ofLogError() << "Cannot set blendmode for attachment with index above 7. Given index: " << attachmentIdx; + return; + } + auto & attachmentState = blendAttachmentStates[attachmentIdx]; + + switch ( mode ) { + case BlendMode::ePremultipliedAlpha: + attachmentState + .setBlendEnable( VK_TRUE ) + .setColorBlendOp( ::vk::BlendOp::eAdd ) + .setAlphaBlendOp( ::vk::BlendOp::eAdd ) + .setSrcColorBlendFactor( ::vk::BlendFactor::eOne ) + .setDstColorBlendFactor( ::vk::BlendFactor::eOneMinusSrcAlpha ) + .setSrcAlphaBlendFactor( ::vk::BlendFactor::eOne ) + .setDstAlphaBlendFactor( ::vk::BlendFactor::eZero ) + ; + break; + case BlendMode::eAlpha: + attachmentState + .setBlendEnable( VK_TRUE ) + .setColorBlendOp( ::vk::BlendOp::eAdd ) + .setAlphaBlendOp( ::vk::BlendOp::eAdd ) + .setSrcColorBlendFactor( ::vk::BlendFactor::eSrcAlpha ) + .setDstColorBlendFactor( ::vk::BlendFactor::eOneMinusSrcAlpha ) + .setSrcAlphaBlendFactor( ::vk::BlendFactor::eOne ) + .setDstAlphaBlendFactor( ::vk::BlendFactor::eZero ) + ; + break; + case BlendMode::eScreen: + attachmentState + .setBlendEnable( VK_TRUE ) + .setColorBlendOp( ::vk::BlendOp::eAdd ) + .setAlphaBlendOp( ::vk::BlendOp::eAdd ) + .setSrcColorBlendFactor( ::vk::BlendFactor::eOne ) // fragment shader output assumed to be premultiplied alpha! + .setDstColorBlendFactor( ::vk::BlendFactor::eOne ) // + .setSrcAlphaBlendFactor( ::vk::BlendFactor::eZero ) // + .setDstAlphaBlendFactor( ::vk::BlendFactor::eZero ) // + ; + break; + default: + break; + } +} + +// ---------------------------------------------------------------------- + +::vk::Pipeline GraphicsPipelineState::createPipeline( const ::vk::Device & device, const std::shared_ptr<::vk::PipelineCache> & pipelineCache, ::vk::Pipeline basePipelineHandle_ ){ + ::vk::Pipeline pipeline; + + // naive: create a pipeline based on current internal state + + // TODO: make sure pipeline is not already in current cache + // otherwise return handle to cached pipeline - instead + // of moving a new pipeline out, return a handle to + // a borrowed pipeline. + + + // derive stages from shader + // TODO: only re-assign if shader has changed. + auto stageCreateInfo = mShader->getShaderStageCreateInfo(); + + ::vk::PipelineCreateFlags createFlags; + + if ( basePipelineHandle_ ){ + // if we already have a base pipeline handle, + // this means we want to create the next pipeline as + // a derivative of the previous pipeline. + createFlags |= ::vk::PipelineCreateFlagBits::eDerivative; + } else{ + // if we have got no base pipeline handle, + // we want to signal that this pipeline is not derived from any other, + // but may allow derivative pipelines. + createFlags |= ::vk::PipelineCreateFlagBits::eAllowDerivatives; + } + + + // make sure pointers to internal vectors and arrays are valid: + + colorBlendState + .setPAttachments ( blendAttachmentStates.data() ) + ; + + dynamicState + .setDynamicStateCount ( dynamicStates.size() ) + .setPDynamicStates ( dynamicStates.data() ) + ; + + // create pipeline info object based on current pipeline object state + + ::vk::GraphicsPipelineCreateInfo pipelineCreateInfo; + + pipelineCreateInfo + .setFlags ( createFlags ) + .setStageCount ( stageCreateInfo.size() ) + .setPStages ( stageCreateInfo.data() ) + .setPVertexInputState ( &mShader->getVertexInputState() ) + .setPInputAssemblyState ( &inputAssemblyState ) + .setPTessellationState ( &tessellationState ) + .setPViewportState ( &viewportState ) + .setPRasterizationState ( &rasterizationState ) + .setPMultisampleState ( &multisampleState ) + .setPDepthStencilState ( &depthStencilState ) + .setPColorBlendState ( &colorBlendState ) + .setPDynamicState ( &dynamicState ) + .setLayout ( *mShader->getPipelineLayout() ) + .setRenderPass ( mRenderPass ) + .setSubpass ( mSubpass ) + .setBasePipelineHandle ( basePipelineHandle_ ) + .setBasePipelineIndex ( mBasePipelineIndex ) + ; + + pipeline = device.createGraphicsPipeline( *pipelineCache, pipelineCreateInfo ); + + // reset internal pointers, so hashing works again + + colorBlendState + .setPAttachments( nullptr ) + ; + + dynamicState + .setPDynamicStates( nullptr ) + ; + + mDirty = false; + + return pipeline; +} + +// ---------------------------------------------------------------------- + +bool GraphicsPipelineState::operator==( GraphicsPipelineState const & rhs ){ + return mRenderPass == rhs.mRenderPass + && mSubpass == rhs.mSubpass + && mShader->getShaderCodeHash() == rhs.mShader->getShaderCodeHash() + && inputAssemblyState == rhs.inputAssemblyState + && tessellationState == rhs.tessellationState + && viewportState == rhs.viewportState + && rasterizationState == rhs.rasterizationState + && multisampleState == rhs.multisampleState + && depthStencilState == rhs.depthStencilState + && colorBlendState == rhs.colorBlendState + && dynamicState == rhs.dynamicState + ; +} + +// ---------------------------------------------------------------------- + +uint64_t GraphicsPipelineState::calculateHash() const { + + std::vector setLayoutKeys = mShader->getDescriptorSetLayoutKeys(); + + uint64_t hash = mShader->getShaderCodeHash(); + + hash = SpookyHash::Hash64( setLayoutKeys.data(), sizeof( uint64_t ) * setLayoutKeys.size(), hash ); + + hash = SpookyHash::Hash64( (void*) &inputAssemblyState, sizeof( inputAssemblyState ), hash ); + hash = SpookyHash::Hash64( (void*) &tessellationState, sizeof( tessellationState ), hash ); + hash = SpookyHash::Hash64( (void*) &viewportState, sizeof( viewportState ), hash ); + hash = SpookyHash::Hash64( (void*) &rasterizationState, sizeof( rasterizationState ), hash ); + hash = SpookyHash::Hash64( (void*) &multisampleState, sizeof( multisampleState ), hash ); + hash = SpookyHash::Hash64( (void*) &depthStencilState, sizeof( depthStencilState ), hash ); + hash = SpookyHash::Hash64( (void*) &dynamicStates, sizeof( dynamicStates ), hash ); + hash = SpookyHash::Hash64( (void*) &blendAttachmentStates, sizeof( blendAttachmentStates ), hash ); + hash = SpookyHash::Hash64( (void*) &colorBlendState, sizeof( colorBlendState ), hash ); + hash = SpookyHash::Hash64( (void*) &dynamicState, sizeof( dynamicState ), hash ); + hash = SpookyHash::Hash64( (void*) &mRenderPass, sizeof( mRenderPass ), hash ); + hash = SpookyHash::Hash64( (void*) &mSubpass, sizeof( mSubpass ), hash ); + + // ofLog() << "pipeline hash:" << std::hex << hash; + return hash; +} + +// ---------------------------------------------------------------------- + +void GraphicsPipelineState::setShader( const std::shared_ptr& shader ){ + if ( shader.get() != mShader.get() ){ + mShader = shader; + mDirty = true; + } +} + + +// ---------------------------------------------------------------------- + +void GraphicsPipelineState::touchShader() const{ + mDirty = mShader->compile(); +} + +// ---------------------------------------------------------------------- diff --git a/libs/openFrameworks/vk/Pipeline.h b/libs/openFrameworks/vk/Pipeline.h new file mode 100644 index 00000000000..853a79627a2 --- /dev/null +++ b/libs/openFrameworks/vk/Pipeline.h @@ -0,0 +1,229 @@ +#pragma once + +#include "vulkan/vulkan.hpp" +#include +#include + +#include "ofFileUtils.h" +#include "ofLog.h" + +/* + +A pipeline is a monolithic compiled object that represents +all the programmable, and non-dynamic state affecting a +draw call. + +You can look at it as a GPU program combining shader machine +code with gpu-hardware-specific machine code dealing with +blending, primitive assembly, etc. + +The Pipeline has a layout, that's the "function signature" so +to say, for the uniform parameters. You feed these parameters +when you bind descriptor sets to the command buffer which you +are currently recording. A pipeline bound to the same command +buffer will then use these inputs. + +Note that you *don't* bind to the pipeline directly, +but you bind both pipeline layout and descriptor sets +TO THE CURRENT COMMAND BUFFER. + +Imagine the Command Buffer as the plugboard, and the Pipeline +Layout plugging wires in on one side, and the descriptor sets +plugging wires in on the other side. + +A pipeline can have some dynamic state, that is state which is +controlled by the command buffer. State which may be dynamic is +pretty limited, and has to be defined when you create a pipeline. + +When a pipeline is created it is effectively compiled into a +GPU program. Different non-dynamic pipeline state needs a different +pipeline. That's why you potentially need a pipeline for all +possible combinations of states that you may use. + +## Mission Statement + +This class helps you create pipelines, and it also wraps pipeline +caching, so that pipelines can be requested based on dynamic state +and will be either created dynamically or created upfront. + +This class shall also help you to create pipeline layouts, based +on how your shaders are defined. It will try to match shader +information gained through reflection (using spriv-cross) with +descriptorSetLayouts to see if things are compatible. + +The API will return VK handles, so that other libraries can be used +on top or alternatively to this one. + + +*/ + +namespace of{ +namespace vk{ + +class Shader; + +// ------------------------------------------------------------ + +class ComputePipelineState +{ + +private: + + int32_t mBasePipelineIndex = -1; + mutable VkBool32 mDirty = true; // whether this pipeline state is dirty. + + std::shared_ptr mShader; // shader allows us to derive pipeline layout, has public getters and setters. + +public: + + const std::shared_ptr& getShader() const; + void setShader( const std::shared_ptr & shader ); + void touchShader() const; + + ::vk::Pipeline createPipeline( const ::vk::Device& device, const std::shared_ptr<::vk::PipelineCache>& pipelineCache, ::vk::Pipeline basePipelineHandle = nullptr ); + + uint64_t calculateHash() const; + + bool operator== ( ComputePipelineState const & rhs ); + bool operator!= ( ComputePipelineState const & rhs ){ + return !operator==( rhs ); + }; + +}; + +// ------------------------------------------------------------ + +class GraphicsPipelineState +{ + + // when we build the command buffer, we need to check + // if the current context state is matched by an already + // available pipeline. + // + // if it isn't, we have to compile a pipeline for the command + // + // if it is, we bind that pipeline. + + +public: // these states can be set upfront + + ::vk::PipelineInputAssemblyStateCreateInfo inputAssemblyState; + ::vk::PipelineTessellationStateCreateInfo tessellationState; + ::vk::PipelineViewportStateCreateInfo viewportState; + ::vk::PipelineRasterizationStateCreateInfo rasterizationState; + ::vk::PipelineMultisampleStateCreateInfo multisampleState; + ::vk::PipelineDepthStencilStateCreateInfo depthStencilState; + + std::array<::vk::DynamicState, 2> dynamicStates; + std::array<::vk::PipelineColorBlendAttachmentState,8> blendAttachmentStates; // 8 == max color attachments. + + ::vk::PipelineColorBlendStateCreateInfo colorBlendState; + ::vk::PipelineDynamicStateCreateInfo dynamicState; + +private: // these states must be received through context + + // non-owning - note that renderpass may be inherited from a + // primary command buffer. + mutable ::vk::RenderPass mRenderPass; + mutable uint32_t mSubpass = 0; + +private: + int32_t mBasePipelineIndex = -1; + + // shader allows us to derive pipeline layout, has public getters and setters. + std::shared_ptr mShader; + + // whether this pipeline state is dirty. + mutable VkBool32 mDirty = true; + +public: + + // We define some blendmodes for convenience so that its less of a nightmare to + // populate blendAttachmentStates. + enum class BlendMode: uint32_t { + ePremultipliedAlpha, + eAlpha, + eScreen, + }; + + void setBlendMode( uint8_t attachmentIdx, BlendMode ); + + GraphicsPipelineState(); + + void reset(); + + uint64_t calculateHash() const; + + + const std::shared_ptr getShader() const; + void setShader( const std::shared_ptr & shader ); + void touchShader() const; + + void setRenderPass( const ::vk::RenderPass& renderPass ) const { + if ( renderPass != mRenderPass ){ + mRenderPass = renderPass; + mDirty = true; + } + } + + const ::vk::RenderPass & getRenderPass() const{ + return mRenderPass; + } + + void setSubPass( uint32_t subpassId ) const { + if ( subpassId != mSubpass ){ + mSubpass = subpassId; + mDirty = true; + } + } + + ::vk::Pipeline createPipeline( const ::vk::Device& device, const std::shared_ptr<::vk::PipelineCache>& pipelineCache, ::vk::Pipeline basePipelineHandle = nullptr ); + + bool operator== ( GraphicsPipelineState const & rhs ); + bool operator!= ( GraphicsPipelineState const & rhs ){ + return !operator==( rhs ); + }; + +}; + +// ---------------------------------------------------------------------- + +/// \brief Create a pipeline cache object +/// \detail Optionally load from disk, if filepath given. +/// \note Ownership: passed on. +static inline std::shared_ptr<::vk::PipelineCache> createPipelineCache( const ::vk::Device& device, std::string filePath = "" ){ + ::vk::PipelineCache cache; + + ofBuffer cacheFileBuffer; + ::vk::PipelineCacheCreateInfo info; + + if ( ofFile( filePath ).exists() ){ + cacheFileBuffer = ofBufferFromFile( filePath, true ); + info.setInitialDataSize( cacheFileBuffer.size() ); + info.setPInitialData( cacheFileBuffer.getData() ); + } + + auto result = std::shared_ptr<::vk::PipelineCache>( + new ::vk::PipelineCache( device.createPipelineCache( info ) ), [d = device]( ::vk::PipelineCache* rhs ){ + if ( rhs ){ + d.destroyPipelineCache( *rhs ); + delete( rhs ); + } + } ); + + return result; +}; + +} // namespace vk +} // namespace of + +// ---------------------------------------------------------------------- + +inline const std::shared_ptr of::vk::GraphicsPipelineState::getShader() const{ + return mShader; +} + +inline const std::shared_ptr& of::vk::ComputePipelineState::getShader() const{ + return mShader; +} + diff --git a/libs/openFrameworks/vk/README.md b/libs/openFrameworks/vk/README.md new file mode 100644 index 00000000000..74f6154cfc7 --- /dev/null +++ b/libs/openFrameworks/vk/README.md @@ -0,0 +1,322 @@ + +# Experimental Renderer using the Vulkan API + +This renderer and its API is experimental. Expect things to change without warning +all the time. + +## Switch between GL and VK rendering mode + +To switch between Vulkan and GL for rendering API, toggle the +following `#define` near the top of `ofConstants.h`: + + #define OF_TARGET_API_VULKAN + +Unfortunately, because of how `ofGLFWWindow` is organised currently, it +is not trivial to switch between Vulkan and GL for target APIs. Using +the #define seemed the most straightforward way to do it, but ideally, +you should be able to feed an `ofVkWindowSettings` object to +`ofCreateWindow` and that should be it. A future feature. + +## Setup + +This has been developed and tested on Vulkan SDK 1.0.8 up to Vulkan SDK 1.0.46, on Windows, and Linux (ubuntu 16.06, and 17), with NVIDIA drivers. Other Vulkan capable systems/GPUs are expected to work, most proably requiring slight modifications. + + +### Install the Vulkan SDK from LunarG + +Download the matching SDK for your system from: https://vulkan.lunarg.com + +It is recommended that you download an **Vulkan SDK version of 1.0.46 or above**. The Vulkan SDK library search paths have changed somewhere around SDK verions 1.0.42 and above - and the openFrameworks Vulkan renderer expects you're running the latest Vulkan SDK. + +#### Windows + +* Run the installer .exe to install the SDK. +* Check if the vulkan runtime installed successfully. + +This installer should have automatically installed VulkanRT, which is the Vulkan runtime. If not, check out the toubleshooting section. + +##### Troubleshooting: [Windows, sdk 1.0.21.1] + +Check VulkanRT install- I ran into a repeated issue with the powershell script included with the VulkanRT installer failing to execute. This script, `.\ConfigLayersAndVulkanDLL.ps1` is responsible for setting up some registry values to tell the Vulkan loader where to find the validation layers. I found that manually executing the script twice (first run fails) using an Admin Powershell console helped. + + cd "C:\Program Files (x86)\VulkanRT\1.0.21.1" + .\ConfigLayersAndVulkanDLL.ps1 1 64 + # and then again! + .\ConfigLayersAndVulkanDLL.ps1 1 64 + # this time there should be no errors. + +##### Troubleshooting: [Windows, SDK search paths] + +I saw that under windows sometimes the vulkan search paths are not properly set. If your app crashes in debug mode because vulkan layers cannot be found, type + + echo $VK_SDK_PATH + +and + + echo $VK_LAYER_PATH + +To see if these search paths are pointing to where you installed the Vulkan SDK. By default, `VK_LAYER_PATH` should point to a subdirectory of `VK_SDK_PATH`, named `Bin` + +#### Linux + +* Run the SDK archive package - it will expand into a folder, +* Copy it into `~/sdks/VulkanSDK` + +Now you will need to set some runtime variables for debug programs so that the debug layers can be found. I'm using a shell script to set up these system-wide ENV variables. Note that you will have to replace the correct path to your latest vulkan sdk directory: + + # This script lives at: /etc/profile.d/vk-environment.sh + # Set VULKAN environment variables. + export VULKAN_SDK=/home/tim/sdks/VulkanSDK/1.0.21.1/x86_64 # <-- replace with your own path to VK_SDK here + export PATH="$VULKAN_SDK/bin:$PATH" + export LD_LIBRARY_PATH=$VULKAN_SDK/lib + export VK_LAYER_PATH=$VULKAN_SDK/etc/explicit_layer.d + +You might have to log out and back in again, for the variables to take effect. + +Run `vulkaninfo` to see which version of the SDK you are running. (It will say it in the fist line.) + +### Install openFrameworks dependencies + +For Windows, use the command line to navigate to the `scripts/vs` directory. There, execute: + + powershell -File download_libs.ps1 + +This will update and install the latest library dependencies used for openFrameworks, precompiled. + +### Clone apothecary + +Apothecary is openFrameworks' dependency tracker and libraries build helper. For openFrameworks-vk it is needed to build the latest versions of `GLFW`, and `shaderc`. + +To get apothecary with some extra build recipes needed for Vulkan, clone it from here: + + git clone https://github.com/openFrameworks-vk/apothecary apothecary + +Apothecary requires a linux-like environment, on Windows, the most reliable for apothecary is MinGW64, wich comes bundled when you install git for windows. (https://git-for-windows.github.io/). + +Then, move into the apothecary base directory. + + cd apothecary/apothecary + +### Update GLFW dependency using apothecary + +You might be fine running the latest version of GLFW which comes bundled with openFrameworks. In case you need a more recent version of GLFW, you can use apothecary to compile it for you. + +For Windows, visual studio 2015, and 64 bit do: + + ./apothecary -a 64 -t vs update glfw + +For Linux do: + + ./apothecary -a 64 update glfw + +### Update/Create shaderc dependency using apothecary + +ShaderC is the shader compiler which we use to compile GLSL shader code to Vulkan's SPIR-V intermediate shader language. + +If you are on windows, you might want to check if apothecary has access to python. Python is required to build shaderc. In a mingw terminal, issue: + + python --version + +If python is installed, you should see a version number, otherwise, install python for windows (3.5+) from here: https://www.python.org/downloads/windows/. Make sure you tick "Add Python to PATH" so that python is accessible from the console. + +To compile shaderc, for Windows, visual studio 2015, and 64 bit (recommended) do: + + ./apothecary -a 64 -t vs update shaderc + +To compile shaderc on linux do: + + ./apothecary -a 64 update shaderc + +If compiling fails for any reason, delete the build folder in apothecary, read over the instructions again, and see if something might have been missed, then issue the above command again. I found that on Windows, with the ConEmu terminal manager, the PATH for python was not set correctly, and that running apothecary from the default "git for windows" console worked flawlessly. + +### Copy dependencies + +Once you have the dependencies compiled, move to the base apothecary directory, where you will find two new directories with the build results: + + glfw + shaderc + +copy these two directories into: + + openframeworks-vk/libs/ + +when asked, select "replace" to overwrite the old libraries in-place. + +### Open Test Project + +In apps/devApps you'll find a project called `vkDemoFlag`, that's an example made for Vulkan. There are some more in the same directory, with 'vk' in their name. + +---------------------------------------------------------------------- + +## The Grand Plan + +This renderer adds Vulkan support to openFrameworks. + +Since Vulkan is a much more explicit API than OpenGL and a rather +fundamental architectural shift, this Renderer does not aim to be a +replacement for OpenGL, but a modern middle layer for rendering, processing & tinkering with Vulkan within an openFrameworks environment. + +Vulkan assumes that you know what you are doing. It also assumes that +you are very, very explicit about what you are doing. + +This renderer tries to keep things fairly fun by taking care of the +boilerplate, and to provide - where this makes sense - prefabricated +building blocks pre-initialised to sensible defaults. + +The idea is that you might want to prototype your app by slapping +together some of these building blocks, and then, replacing some of +these blocks (the ones that actually make a difference in speed, usability or aesthetics) with customised elements. + +Advanced users will probably want to write their own scene graphs, renderer addons etc. Great! We should make sure that this is possible. + +---------------------------------------------------------------------- + +## Architecture + +Since Vulkan is not backwards-compatible with OpenGL, the renderer does not have this ambition either, and instead aims at making Vulkan more accessible, with an emphasis on things (shader hot-reloading, etc.) that may be useful for openFrameworks. + +Some architectural ideas are borrowed from modern engines and projects such as [bgfx][bgfx] or [regl.party][regl], which I highly suggest studying. + +A key element in these modern approaches is to get away from stateful immediate-mode drawing as in "classic" OpenGL, to a more declarative state-less, data-driven, approach. This makes it possible engine-side to do optimisations underneath, and fits much better into how Vulkan and the GPU itself is laid out. + +In principle, you first define *how* something will be drawn (what shader, what primitive mode, whether you want to use a depth test), and all these choices become consolidated and compiled into pipeline. + +Then, you define *what* to draw, which is the "data" in "data"-driven, so to say. + +### DrawCommand + +In the openFrameworks Vulkan renderer, a `DrawCommand` holds all the data needed to draw using the pipeline the `DrawCommand` was created from: (which mesh to draw, uniform settings for the shader etc). Notice that this broadly maps to the uniforms and attributes, samplers, etc. declared in the GLSL code for the shader you're using to draw. + +### RenderBatch + +To send a `DrawCommand` through the pipeline, you first need to create a `RenderBatch`. This is an object which helps accumulate multiple draw commands, and forward them down the engine in one go. A `RenderBatch` is a temporary object, and it is created from a `Context`, it also encapsulates a vulkan Renderpass, and is translated by the engine into a single vulkan CommandBuffer. + +---------------------------------------------------------------------- + +### Context + +A `Context` is an isolated environment where temporary objects and buffer memory can +be allocated in an optimised way. + +A `Context` keeps memory isolated per *Virtual Frame*. A Virtual frame is protected by a +`vk::Fence` which is set on `Context::end()` and waited upon `Context::begin()`. +It is therefore safe to assume that a Context is ready for write after `Context::begin()`. + +All `Context` operations are meant to be thread-safe +as long as the `Context` and its dependents never leaves its home thread. All objects allocated through +a context are synchronised using a fence which keeps all objects alife until the +frame is re-visited. + +The default `Context` draws to the screen, and you can grab it by calling the `ofVkRenderer`: + + auto renderer = dynamic_pointer_cast( ofGetCurrentRenderer() ); + auto & context = renderer->getDefaultContext() + +To render using a `Context`, create a `RenderBatch` from the Context, `.begin()` the RenderBatch, and add `DrawCommand`s into it by calling `.draw(myDrawCommand)`. When you are done drawing, `.end()` the RenderBatch. + +When the RenderBatch ends, its internal queue of DrawCommands is translated into a single `vk::Commandbuffer`, which is in turn queued inside the Context. + + of::vk::RenderBatch batch{ batchSettings }; + + batch.begin(); + batch + .draw( myDrawCommand_1 ) + .draw( myDrawCommand_2 ) + .draw( myDrawCommand_3 ) + ; + // ... + batch.end(); + + +When the Context ends, its internal queue of `vk::CommandBuffer`s is submitted to the graphics `vk::Queue` for rendering. + +---------------------------------------------------------------------- + +## Allocator + +Vulkan requires you to do your own memory management and allocations. + +Allocator is a simple, linear GPU memory allocator written especially for dynamic memory. Dynamic memory is memory which changes every frame (think your matrices, and uniforms), in contrast to memory which is static (think your scene geometry). + +For the context, the Allocator will pre-allocate about 32MB of memory *per swapchain frame* and map this to a *single* buffer and a *single* chunk of physical memory. + +Whenever you request memory from the Allocator, it returns an address to mapped CPU memory you can write into - and an offset into the GPU buffer which you can use to offset your draw binding position to use the memory you +just wrote. + +We're doing this because the cost of allocating memory is immense compared +to everything else, and the complexity of having lots of buffers flying +around is mind-boggling. We're allocating all dynamic memory on renderer setup. And we're returning all the memory on renderer teardown. + +By using a single buffer we can move all memory for a frame from CPU visible memory to GPU-only visible memory in parallel - on a transfer queue if that makes sense. The transfer is for the whole range of a frame (32 MB) - and ideally done on a paralell, transfer-only queue. This is a common technique for triple-buffering. + +Most cards without special VRAM such as Intel integrated cards are perfectly happy not to do this, but there might be benefits for NVidia cards for example, which have faster, GPU-only visible memory. + + + +---------------------------------------------------------------------- + +## SPIR-V Cross + +Vulkan introduces a new intermediary shader language, SPIR-V. Vulkan only accepts SPIR-V as shader language. SPIR-V files come precompiled, as a blob of 32bit words. + +You can generate SPIR-V from GLSL source using the `glslLangValidator`, which is part of the Vulkan SDK. + +Spir-V Cross, which is included in source within openFrameworks/vk, +allows us to do reflection at runtime on these shader programs. This +makes it possible to derive our pipeline binding points much less +painfully, and on the fly. + +It could also allow us to cross-compile spirv shader code to GLSL or +even .cpp if we wanted, which is pretty nifty. + +---------------------------------------------------------------------- + +## ShaderC + +To compile glsl shader code into SPIR-V, we include shaderc, a static library which is the reference GLSL shader compiler with some helpers and syntactic sugar added by Google, who maintain this project. + +There is a recipe for shaderc included in `https://github.com/openframeworks-vk/apothecary`. The recipe file contains hints in the comments on the command line parameters to invoke on the recipe depending on the target operating system. + +This allows us to ingest shaders as GLSL, and to print out error messages if there was a compilation error. If there was a compilation error, `vk::Shader` will print out the context of the offending line alongside the compilation message. If a previous version of the shader compiled successfully, shader compilation aborts at this point, and the previous shader is used for rendering until the new version compiles successfully. This helps when prototyping. + +The Vulkan renderer uses shaderC to make `#include` statements possible in glsl shader code. Any errors found when compiling includes is printed out with the correct line number of the offending include file, together with some lines of context of where the shader error was found. + +---------------------------------------------------------------------- + +## Vulkan Quirks + ++ In Screen Space, Vulkan flips Y, compared to OpenGL. ++ Vulkan does not use an unit cube for the view frustum, the frustum + has half-depth (z does from 0 to +1 instead of -1 to +1) ++ To deal with the two points above, we pre-multiply the projection + matrix with a clip matrix: + +```cpp +ofMatrix4x4 clip(1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, -1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.5f, 0.0f, + 0.0f, 0.0f, 0.5f, 1.0f); +``` + +---------------------------------------------------------------------- + +# Vulkan Resources + +* Khronos Vulkan spec ([HTML][spec]) +* [Awesome Vulkan][awesome] -- A curated collection of links to resources around Vulkan + +---------------------------------------------------------------------- + +# Design Principles + +1. Make it work +2. Make it correct +3. Make it fast +4. Make it simple + +---------------------------------------------------------------------- + +[spec]: https://www.khronos.org/registry/vulkan/specs/1.0-extensions/html/vkspec.html +[awesome]: https://github.com/vinjn/awesome-vulkan +[regl]: http://regl.party/ +[bgfx]: https://github.com/bkaradzic/bgfx diff --git a/libs/openFrameworks/vk/RenderBatch.cpp b/libs/openFrameworks/vk/RenderBatch.cpp new file mode 100644 index 00000000000..6a76ca7312a --- /dev/null +++ b/libs/openFrameworks/vk/RenderBatch.cpp @@ -0,0 +1,335 @@ +#include "vk/RenderBatch.h" +#include "vk/spooky/SpookyV2.h" +#include "vk/Shader.h" + +using namespace std; +using namespace of::vk; + +// ------------------------------------------------------------ + +RenderBatch::RenderBatch( RenderBatch::Settings& settings ) + : mSettings( settings ) +{ + auto & context = *mSettings.context; + // Allocate a new command buffer for this batch. + mVkCmd = context.allocateCommandBuffer( ::vk::CommandBufferLevel::ePrimary ); + mVkCmd.begin( { ::vk::CommandBufferUsageFlagBits::eOneTimeSubmit } ); +} + +// ------------------------------------------------------------ + +of::vk::RenderBatch& RenderBatch::draw( const DrawCommand& dc_ ){ + + // local copy of draw command. + DrawCommand dc = dc_; + + finalizeDrawCommand( dc ); + + mDrawCommands.emplace_back( std::move(dc) ); + + return *this; +} + +// ---------------------------------------------------------------------- + +RenderBatch & of::vk::RenderBatch::draw( const DrawCommand & dc_, uint32_t vertexCount_, uint32_t instanceCount_, uint32_t firstVertex_, uint32_t firstInstance_ ){ + + // local copy of draw command. + DrawCommand dc = dc_; + + finalizeDrawCommand( dc ); + + dc.mDrawMethod = DrawCommand::DrawMethod::eDraw; + dc.mNumVertices = vertexCount_; + dc.mInstanceCount = instanceCount_; + dc.mFirstVertex = firstVertex_; + dc.mFirstInstance = firstInstance_; + + mDrawCommands.emplace_back( std::move( dc ) ); + + return *this; +} + +// ---------------------------------------------------------------------- + +RenderBatch & of::vk::RenderBatch::draw( const DrawCommand & dc_, uint32_t indexCount_, uint32_t instanceCount_, uint32_t firstIndex_, int32_t vertexOffset_, uint32_t firstInstance_ ){ + + // local copy of draw command. + DrawCommand dc = dc_; + + finalizeDrawCommand( dc ); + + dc.mDrawMethod = DrawCommand::DrawMethod::eIndexed; + dc.mNumIndices = indexCount_; + dc.mInstanceCount = instanceCount_; + dc.mFirstIndex = firstIndex_; + dc.mVertexOffset = vertexOffset_; + dc.mFirstInstance = firstInstance_; + + mDrawCommands.emplace_back( std::move( dc ) ); + + return *this; +} + +// ---------------------------------------------------------------------- + +void of::vk::RenderBatch::finalizeDrawCommand( of::vk::DrawCommand &dc ){ + // Commit draw command memory to gpu + // This will update dynamic offsets as a side-effect, + // and will also update the buffer ID for the bindings affected. + dc.commitUniforms( mSettings.context->getAllocator() ); + dc.commitMeshAttributes( mSettings.context->getAllocator() ); + + // Renderpass is constant over a RenderBatch, as a RenderBatch encapsulates + // a renderpass with all its subpasses. + dc.mPipelineState.setRenderPass( mSettings.renderPass ); + dc.mPipelineState.setSubPass( mVkSubPassId ); +} + +// ---------------------------------------------------------------------- + +void RenderBatch::begin(){ + + // TODO: When runing in debug, check for RenderBatch state: cannot begin when not ended, or not initial + + auto & context = *mSettings.context; + + if ( mSettings.renderPass ){ + + // Begin Renderpass - + // This is only allowed if the context maps a primary renderpass! + + // Note that secondary command buffers inherit the renderpass + // from their primary. + + // Create a new Framebuffer. The framebuffer connects RenderPass with + // ImageViews where results of this renderpass will be stored. + // + ::vk::FramebufferCreateInfo framebufferCreateInfo; + framebufferCreateInfo + .setRenderPass( mSettings.renderPass ) + .setAttachmentCount( mSettings.framebufferAttachments.size() ) + .setPAttachments( mSettings.framebufferAttachments.data() ) + .setWidth( mSettings.framebufferAttachmentsWidth ) + .setHeight( mSettings.framebufferAttachmentsHeight ) + .setLayers( 1 ) + ; + + // Framebuffers in Vulkan are very light-weight objects, whose main purpose + // is to connect RenderPasses to Image attachments. + // + // Since the swapchain might have a different number of images than this context has virtual + // frames, and the swapchain may even acquire images out-of-sequence, we must re-create the + // framebuffer on each frame to make sure we're attaching the renderpass to the correct + // attachments. + // + // We create framebuffers through the context, so that the context + // can free all old framebuffers once the frame has been processed. + mFramebuffer = context.createFramebuffer( framebufferCreateInfo ); + + ::vk::RenderPassBeginInfo renderPassBeginInfo; + renderPassBeginInfo + .setRenderPass( mSettings.renderPass ) + .setFramebuffer( mFramebuffer ) + .setRenderArea( mSettings.renderArea ) + .setClearValueCount( mSettings.clearValues.size() ) + .setPClearValues( mSettings.clearValues.data() ) + ; + + mVkCmd.beginRenderPass( renderPassBeginInfo, ::vk::SubpassContents::eInline ); + } + + // Set dynamic viewport + // TODO: these dynamics may belong to the draw command + ::vk::Viewport vp; + vp + .setX( 0 ) + .setY( 0 ) + .setWidth( mSettings.renderArea.extent.width ) + .setHeight( mSettings.renderArea.extent.height ) + .setMinDepth( 0.f ) + .setMaxDepth( 1.f ) + ; + mVkCmd.setViewport( 0, { vp } ); + mVkCmd.setScissor( 0, { mSettings.renderArea } ); +} + +// ---------------------------------------------------------------------- + +void RenderBatch::end(){ + // submit command buffer to context. + // context will submit command buffers batched to queue + // at its own pleasure, but in seqence. + + processDrawCommands(); + + if ( mSettings.renderPass ){ + // end renderpass if Context / CommandBuffer is Primary + mVkCmd.endRenderPass(); + } + + mVkCmd.end(); + + auto & context = const_cast( *mSettings.context ); + + // add command buffer to command queue of context. + context.submit( std::move( mVkCmd ) ); + + mVkCmd = nullptr; + mDrawCommands.clear(); +} + +// ---------------------------------------------------------------------- + +::vk::CommandBuffer & RenderBatch::getVkCommandBuffer(){ + // Flush currently queued up draw commands so that command buffer + // is in the right state + processDrawCommands(); + return mVkCmd; +} + + +// ---------------------------------------------------------------------- + +void RenderBatch::processDrawCommands( ){ + + auto & context = const_cast( *mSettings.context ); + + // CONSIDER: Order draw commands + // Order by (using radix-sort) + // 1) subpass id, + // 2) pipeline, + // 3) descriptor set usage + + + // current draw state for building command buffer - this is based on parsing the drawCommand list + std::unique_ptr boundPipelineState; + + for ( auto & dc : mDrawCommands ){ + + // find out pipeline state needed for this draw command + + if ( !boundPipelineState || *boundPipelineState != dc.mPipelineState ){ + // look up pipeline in pipeline cache + // otherwise, create a new pipeline, then bind pipeline. + + boundPipelineState = std::make_unique( dc.mPipelineState ); + + uint64_t pipelineStateHash = boundPipelineState->calculateHash(); + + auto & currentPipeline = context.borrowPipeline( pipelineStateHash ); + + if ( currentPipeline.get() == nullptr ){ + currentPipeline = + std::shared_ptr<::vk::Pipeline>( ( new ::vk::Pipeline ), + [device = context.mDevice]( ::vk::Pipeline*rhs ){ + if ( rhs ){ + device.destroyPipeline( *rhs ); + } + delete rhs; + } ); + + *currentPipeline = boundPipelineState->createPipeline( context.mDevice, context.mSettings.pipelineCache); + } + + mVkCmd.bindPipeline( ::vk::PipelineBindPoint::eGraphics, *currentPipeline ); + } + + // ----------| invariant: correct pipeline is bound + + // Match currently bound DescriptorSetLayouts against + // dc pipeline DescriptorSetLayouts + std::vector<::vk::DescriptorSet> boundVkDescriptorSets; + std::vector dynamicBindingOffsets; + + const std::vector & setLayoutKeys = dc.mPipelineState.getShader()->getDescriptorSetLayoutKeys(); + + dynamicBindingOffsets.reserve( setLayoutKeys.size() ); + boundVkDescriptorSets.reserve( setLayoutKeys.size() ); + + for ( size_t setId = 0; setId != setLayoutKeys.size(); ++setId ){ + + uint64_t setLayoutKey = setLayoutKeys[setId]; + auto & descriptors = dc.getDescriptorSetData( setId ).descriptors; + const auto descriptorSetLayout = dc.mPipelineState.getShader()->getDescriptorSetLayout( setId ); + // calculate hash of descriptorset, combined with descriptor set sampler state + // TODO: can we accelerate this by caching descriptorSet hash inside shader/draw command? + uint64_t descriptorSetHash = SpookyHash::Hash64( descriptors.data(), descriptors.size() * sizeof( DescriptorSetData_t::DescriptorData_t ), setLayoutKey ); + + // Receive a DescriptorSet from the RenderContext's cache. + // The renderContext will allocate and initialise a DescriptorSet if none has been found. + const ::vk::DescriptorSet& descriptorSet = context.getDescriptorSet( descriptorSetHash, setId, *descriptorSetLayout , descriptors ); + + boundVkDescriptorSets.emplace_back( descriptorSet ); + + const auto & offsets = dc.getDescriptorSetData( setId ).dynamicBindingOffsets; + + // now append dynamic binding offsets for this set to vector of dynamic offsets for this draw call + dynamicBindingOffsets.insert( dynamicBindingOffsets.end(), offsets.begin(), offsets.end() ); + + } + + // Bind resources + + // Bind dc DescriptorSets to current pipeline descriptor sets + // make sure dynamic UBOs have the correct offsets + if ( !boundVkDescriptorSets.empty() ){ + mVkCmd.bindDescriptorSets( + ::vk::PipelineBindPoint::eGraphics, // use graphics, not compute pipeline + *dc.mPipelineState.getShader()->getPipelineLayout(), // VkPipelineLayout object used to program the bindings. + 0, // firstset: first set index (of the above) to bind to - mDescriptorSet[0] will be bound to pipeline layout [firstset] + boundVkDescriptorSets.size(), // setCount: how many sets to bind + boundVkDescriptorSets.data(), // the descriptor sets to match up with our mPipelineLayout (need to be compatible) + dynamicBindingOffsets.size(), // dynamic offsets count how many dynamic offsets + dynamicBindingOffsets.data() // dynamic offsets for each descriptor + ); + } + + // Bind attributes, and draw + { + + const auto & vertexOffsets = dc.getVertexOffsets(); + const auto & indexOffset = dc.getIndexOffsets(); + + const auto & vertexBuffers = dc.getVertexBuffers(); + const auto & indexBuffer = dc.getIndexBuffer(); + + // TODO: cull vertexOffsets which refer to empty vertex attribute data + // make sure that a pipeline with the correct bindings is bound to match the + // presence or non-presence of mesh data. + + // Bind vertex data buffers to current pipeline. + // The vector indices into bufferRefs, vertexOffsets correspond to [binding numbers] of the currently bound pipeline. + // See Shader.h for an explanation of how this is mapped to shader attribute locations + + if ( !vertexBuffers.empty() ){ + mVkCmd.bindVertexBuffers( 0, vertexBuffers, vertexOffsets ); + } + + switch ( dc.mDrawMethod ){ + case DrawCommand::DrawMethod::eDraw: + // non-indexed draw + mVkCmd.draw( dc.mNumVertices, dc.mInstanceCount, dc.mFirstVertex, dc.mFirstInstance ); + break; + case DrawCommand::DrawMethod::eIndexed: + // indexed draw + mVkCmd.bindIndexBuffer( indexBuffer, indexOffset, ::vk::IndexType::eUint32 ); + mVkCmd.drawIndexed( dc.mNumIndices, dc.mInstanceCount, dc.mFirstIndex, dc.mVertexOffset, dc.mFirstInstance ); + break; + case DrawCommand::DrawMethod::eIndirect: + // TODO: implement + break; + case DrawCommand::DrawMethod::eIndexedIndirect: + // TODO: implement + break; + } + + } + + } + + // remove processed draw commands from queue + mDrawCommands.clear(); + +} + diff --git a/libs/openFrameworks/vk/RenderBatch.h b/libs/openFrameworks/vk/RenderBatch.h new file mode 100644 index 00000000000..b20ad643d51 --- /dev/null +++ b/libs/openFrameworks/vk/RenderBatch.h @@ -0,0 +1,180 @@ +#pragma once + +#include +#include "ofLog.h" +#include "vk/Pipeline.h" +#include "vk/Allocator.h" +#include "vk/DrawCommand.h" +#include "vk/Context.h" + +namespace of { +namespace vk{ + +// ------------------------------------------------------------ + +class RenderBatch +{ +public: + struct Settings + { + Context * context = nullptr; + ::vk::RenderPass renderPass; + std::vector<::vk::ImageView> framebufferAttachments; + uint32_t framebufferAttachmentsWidth = 0; + uint32_t framebufferAttachmentsHeight = 0; + ::vk::Rect2D renderArea {}; + std::vector<::vk::ClearValue> clearValues; // clear values for each attachment + + Settings& setContext( Context* ctx ){ + context = ctx; + return *this; + } + Settings& setRenderPass( const ::vk::RenderPass & renderPass_ ){ + renderPass = renderPass_; + return *this; + } + Settings& setFramebufferAttachments( const std::vector<::vk::ImageView>& framebufferAttachments_ ){ + framebufferAttachments = framebufferAttachments_; + return *this; + } + Settings& setFramebufferAttachmentsWidth( uint32_t width_ ){ + framebufferAttachmentsWidth = width_; + return *this; + } + Settings& setFramebufferAttachmentsHeight( uint32_t height_ ){ + framebufferAttachmentsHeight = height_; + return *this; + } + Settings& setFramebufferAttachmentsExtent( uint32_t width_, uint32_t height_ ){ + framebufferAttachmentsWidth = width_; + framebufferAttachmentsHeight = height_; + return *this; + } + Settings& setRenderArea( const ::vk::Rect2D& renderArea_ ){ + renderArea = renderArea_; + return *this; + } + Settings& setRenderAreaOffset( const ::vk::Offset2D & offset_ ){ + renderArea.setOffset( offset_ ); + return *this; + } + Settings& setRenderAreaExtent( const ::vk::Extent2D & extent_ ){ + renderArea.setExtent( extent_ ); + return *this; + } + Settings& setRenderAreaExtent( uint32_t width_, uint32_t height_ ){ + renderArea.setExtent( { width_, height_ } ); + return *this; + } + Settings& setClearValues( const std::vector<::vk::ClearValue>& clearValues_ ){ + clearValues = clearValues_; + return *this; + } + Settings& addFramebufferAttachment( const ::vk::ImageView& imageView ){ + framebufferAttachments.push_back( imageView ); + return *this; + } + template + Settings& addClearColorValue( const ColorT& color_ ){ + static_assert(sizeof(ColorT) == sizeof(::vk::ClearColorValue), "Color type must be compatible with VkClearColorValue"); + clearValues.emplace_back(reinterpret_cast (color_)); + return *this; + } + Settings& addClearDepthStencilValue( const ::vk::ClearDepthStencilValue depthStencilValue_ ){ + clearValues.emplace_back( depthStencilValue_ ); + return *this; + } + }; + +private: + /* + + A Batch maps to a Primary Command buffer which begins and ends a RenderPass - + as such, it also maps to a framebuffer. + + Batch is an object which processes draw instructions + received through draw command objects. + + Batch's mission is to create a single command buffer from all draw commands + it accumulates, and it aims to minimize the number of pipeline switches + between draw calls. + + */ + + const Settings mSettings; + + ::vk::Framebuffer mFramebuffer; + + uint32_t mVkSubPassId = 0; + std::list mDrawCommands; + + // vulkan command buffer mapped to this batch. + ::vk::CommandBuffer mVkCmd; + + // renderbatch must be constructed from a valid context. + RenderBatch() = delete; + +public: + + RenderBatch( RenderBatch::Settings& settings ); + + ~RenderBatch(){ + if ( !mDrawCommands.empty() ){ + ofLogWarning() << "Unsubmitted draw commands leftover in RenderBatch"; + } + } + + uint32_t nextSubPass(); + + RenderBatch & draw( const DrawCommand& dc); + + // explicit draw - parameters override DrawCommand State + RenderBatch & draw( const DrawCommand& dc, uint32_t vertexCount, uint32_t instanceCount, uint32_t firstVertex, uint32_t firstInstance ); + + // explicit indexed draw - parameters override DrawCommand State + RenderBatch & draw( const DrawCommand& dc, uint32_t indexCount, uint32_t instanceCount, uint32_t firstIndex, int32_t vertexOffset, uint32_t firstInstance ); + + //RenderBatch & drawIndirect( const DrawCommand& dc ); + //RenderBatch & drawIndexedIndirect( const DrawCommand& dc ); + + // Begin command buffer, begin renderpass, + // and also setup default values for scissor and viewport. + void begin(); + + // End renderpass, processes draw commands added to batch, and submits batch translated into commandBuffer to + // context's command buffer queue. + void end(); + + // Return vulkan command buffer mapped to this batch + // n.b. this flushes, i.e. processes all draw commands queued up until this command is called. + ::vk::CommandBuffer& getVkCommandBuffer(); + + // return context associated with this batch + Context* getContext(); + +private: + + void finalizeDrawCommand( of::vk::DrawCommand &dc ); + void processDrawCommands( ); + +}; + +// ---------------------------------------------------------------------- + +inline Context * RenderBatch::getContext(){ + return mSettings.context; +} + + +// ---------------------------------------------------------------------- +// Inside of a renderpass, draw commands may be sorted, to minimize pipeline and binding swaps. +// so endRenderPass should be the point at which the commands are recorded into the command buffer +// If the renderpass allows re-ordering. +inline uint32_t RenderBatch::nextSubPass(){ + return ++mVkSubPassId; +} + +// ---------------------------------------------------------------------- + +} // end namespce of::vk +} // end namespace of \ No newline at end of file diff --git a/libs/openFrameworks/vk/Shader.cpp b/libs/openFrameworks/vk/Shader.cpp new file mode 100644 index 00000000000..069e8de285b --- /dev/null +++ b/libs/openFrameworks/vk/Shader.cpp @@ -0,0 +1,1347 @@ +#include "vk/Shader.h" +#include "ofLog.h" +#include "ofAppRunner.h" +#include "ofFileUtils.h" +#include "spooky/SpookyV2.h" +#include "shaderc/shaderc.hpp" +#include + +using namespace std; + +// ---------------------------------------------------------------------- + +const std::vector of::vk::Shader::Source::defaultShaderVert { + #include "shaders/default.vert.spv" +}; +const std::vector of::vk::Shader::Source::defaultShaderFragGlobalColor { + #include "shaders/default_global_color.frag.spv" +}; +const std::vector of::vk::Shader::Source::defaultShaderFragNormalColor { + #include "shaders/default_normal_color.frag.spv" +}; + +// ---------------------------------------------------------------------- + +namespace of{ +namespace utils{ + +enum class ConsoleColor : uint32_t { + eDefault = 39, + eBrightRed = 91, + eBrightYellow = 93, + eBrightCyan = 96, + eRed = 31, + eYellow = 33, + eCyan = 36, +}; + +// set console colour +std::string setConsoleColor( of::utils::ConsoleColor colour ){ +#if defined( TARGET_WIN32 ) + // On Windows, we need to enable processing of ANSI color sequences. + // We only need to do this the very first time, as the setting + // should stick until the console is closed. + // + static bool needsConsoleModeSetup = true; + if ( needsConsoleModeSetup ){ + HANDLE hConsole = GetStdHandle( STD_OUTPUT_HANDLE ); + // 0x0004 == ENABLE_VIRTUAL_TERMINAL_PROCESSING, see: + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx + DWORD consoleFlags; + GetConsoleMode( hConsole, &consoleFlags ); + consoleFlags |= 0x0004; + SetConsoleMode( hConsole, consoleFlags ); + needsConsoleModeSetup = false; + } +#endif +#if defined (TARGET_LINUX) || defined (TARGET_WIN32) + std::ostringstream tmp; + tmp << "\033[" << reinterpret_cast(colour) << "m"; + return tmp.str(); +#else + return ""; +#endif +} + +// reset console colour +std::string resetConsoleColor(){ + return setConsoleColor(of::utils::ConsoleColor::eDefault); +} + +} // end namespace of::utils +} // end namespace of + + +// ---------------------------------------------------------------------- + +// Responder for file includes through shaderc +class FileIncluder : public shaderc::CompileOptions::IncluderInterface +{ + std::unordered_set mIncludedFiles; /// full set of included files + + struct FileInfo + { + const std::string pathAsString; + std::filesystem::path path; /// path to file + std::vector contents; /// contents of file + }; +public: + // constructor + explicit FileIncluder(){}; + + // Handles shaderc_include_resolver_fn callbacks. + shaderc_include_result* GetInclude( const char* requested_source, shaderc_include_type type, const char* requesting_source, size_t include_depth ) override; + + // Handles shaderc_include_result_release_fn callbacks. + void ReleaseInclude( shaderc_include_result* data ) override; + +}; + +// ---------------------------------------------------------------------- + +// helper method to return error via shaderc +shaderc_include_result* shadercMakeErrorIncludeResult( const char* message ){ + return new shaderc_include_result{ "", 0, message, strlen( message ) }; +} + +// ---------------------------------------------------------------------- + +shaderc_include_result * FileIncluder::GetInclude( const char * requested_source, shaderc_include_type type, const char * requesting_source, size_t include_depth ){ + + std::filesystem::path path = ofToDataPath( requested_source, type == shaderc_include_type::shaderc_include_type_standard ? true : false ); + + FileInfo * newFileInfo = new FileInfo{ path.string(), std::move( path ), std::vector() }; + + if ( false == std::filesystem::exists( newFileInfo->path ) ){ + return shadercMakeErrorIncludeResult( "" ); + } + + auto includeFileBuf = ofBufferFromFile( newFileInfo->path, true ); + + std::vector data; + data.resize( includeFileBuf.size() ); + data.assign( includeFileBuf.begin(), includeFileBuf.end() ); + + newFileInfo->contents = std::move( data ); + + return new shaderc_include_result{ + newFileInfo->pathAsString.data(), newFileInfo->pathAsString.length(), + newFileInfo->contents.data(), newFileInfo->contents.size(), + newFileInfo }; +} + +// ---------------------------------------------------------------------- + +void FileIncluder::ReleaseInclude( shaderc_include_result * include_result ){ + auto fileInfo = reinterpret_cast( include_result->user_data ); + delete fileInfo; + delete include_result; +} + +// ---------------------------------------------------------------------- +// Helper mapping vk shader stage to shaderc shader kind +static shaderc_shader_kind getShaderCKind( const vk::ShaderStageFlagBits &shaderStage ){ + shaderc_shader_kind shaderKind = shaderc_shader_kind::shaderc_glsl_infer_from_source; + switch ( shaderStage ){ + case ::vk::ShaderStageFlagBits::eVertex: + shaderKind = shaderc_shader_kind::shaderc_glsl_default_vertex_shader; + break; + case ::vk::ShaderStageFlagBits::eTessellationControl: + shaderKind = shaderc_shader_kind::shaderc_glsl_default_tess_control_shader; + break; + case ::vk::ShaderStageFlagBits::eTessellationEvaluation: + shaderKind = shaderc_shader_kind::shaderc_glsl_default_tess_evaluation_shader; + break; + case ::vk::ShaderStageFlagBits::eFragment: + shaderKind = shaderc_shader_kind::shaderc_glsl_default_fragment_shader; + break; + case ::vk::ShaderStageFlagBits::eCompute: + shaderKind = shaderc_shader_kind::shaderc_glsl_default_compute_shader; + break; + case ::vk::ShaderStageFlagBits::eGeometry: + shaderKind = shaderc_shader_kind::shaderc_glsl_default_geometry_shader; + break; + default: + break; + } + return shaderKind; +} +// ---------------------------------------------------------------------- + +of::vk::Shader::Shader( const of::vk::Shader::Settings& settings_ ) + : mSettings( settings_ ) +{ + compile(); +} + +// ---------------------------------------------------------------------- + +const uint64_t of::vk::Shader::getShaderCodeHash(){ + if ( mShaderHashDirty ){ + std::vector spirvHashes; + spirvHashes.reserve( mSpvHash.size() ); + for ( const auto&k : mSpvHash ){ + spirvHashes.push_back( k.second ); + } + mShaderHash = SpookyHash::Hash64( spirvHashes.data(), spirvHashes.size() * sizeof( uint64_t ), 0 ); + mShaderHashDirty = false; + } + return mShaderHash; +} + +// ---------------------------------------------------------------------- + +bool of::vk::Shader::compile(){ + bool shaderDirty = false; + + for ( auto & source : mSettings.sources ){ + + auto & shaderStage = source.first; + auto & shaderSource = source.second; + + + bool success = getSpirV( shaderStage, shaderSource); /* load or compiles into spirCode */ + + if ( !success){ + if (!mShaderStages.empty()){ + ofLogError() << "Aborting shader compile. Using previous version of shader instead"; + return false; + } else{ + // We must exit - there is no predictable way to recover from this. + // + // Using a default fail shader would be not without peril: + // Inputs and outputs will most certainly not match whatever + // the user specified for their original shader. + ofLogFatalError() << "Shader did not compile: " << getName() << " : " << shaderSource.getName(); + ofExit( 1 ); + return false; + } + } + + uint64_t spirvHash = SpookyHash::Hash64( reinterpret_cast( shaderSource.spirvCode.data() ), shaderSource.spirvCode.size() * sizeof( uint32_t ), 0 ); + + bool spirCodeDirty = isSpirCodeDirty( shaderStage, spirvHash ); + + if ( spirCodeDirty ){ + createVkShaderModule( shaderStage, shaderSource.spirvCode); + // store hash in map so it does not appear dirty + mSpvHash[shaderStage] = spirvHash; + // copy the ir code buffer into the shader compiler + mSpvCrossCompilers[shaderStage] = make_shared( shaderSource.spirvCode ); + } + + shaderDirty |= spirCodeDirty; + mShaderHashDirty |= spirCodeDirty; + } + + if ( shaderDirty ){ + reflect( mSpvCrossCompilers, mVertexInfo ); + createSetLayouts(); + mPipelineLayout.reset(); + shaderDirty = false; + return true; + } + + return false; +} + +// ---------------------------------------------------------------------- + +bool of::vk::Shader::isSpirCodeDirty( const ::vk::ShaderStageFlagBits shaderStage, uint64_t spirvHash ){ + + if ( mSpvHash.find( shaderStage ) == mSpvHash.end() ){ + // hash not found so must be dirty + return true; + } else{ + return ( mSpvHash[shaderStage] != spirvHash ); + } + + return false; +} + +// ---------------------------------------------------------------------- +inline bool of::vk::Shader::checkForLineNumberModifier( const std::string& line, uint32_t& lineNumber, std::string& currentFilename, std::string& lastFilename ) +{ + + if ( line.find( "#line", 0 ) != 0 ) + return false; + + // --------| invariant: current line is a line number marker + + istringstream is( line ); + + // ignore until first whitespace, then parse linenumber, then parse filename + std::string quotedFileName; + is.ignore( numeric_limits::max(), ' ' ) >> lineNumber >> quotedFileName; + // decrease line number by one, as marker line is not counted + --lineNumber; + // store last filename when change occurs + std::swap( lastFilename, currentFilename ); + // remove double quotes around filename, if any + currentFilename.assign( quotedFileName.begin() + quotedFileName.find_first_not_of( '"' ), quotedFileName.begin() + quotedFileName.find_last_not_of( '"' ) + 1 ); + return true; +} + +// ---------------------------------------------------------------------- + +inline void of::vk::Shader::printError( const std::string& fileName, std::string& errorMessage, std::vector& sourceCode ) { + + ofLogError() << "ERR \tShader compile: " << fileName; + + ofLogError() << of::utils::setConsoleColor( of::utils::ConsoleColor::eBrightRed ) + << errorMessage + << of::utils::resetConsoleColor(); + + std::string errorFileName( 255, '\0' ); // Will contain the name of the file which contains the error + uint32_t lineNumber = 0; // Will contain error line number after successful parse + + // Error string will has the form: "triangle.frag:28: error: '' : syntax error" + auto scanResult = sscanf( errorMessage.c_str(), "%[^:]:%d:", errorFileName.data(), &lineNumber ); + errorFileName.shrink_to_fit(); + + ofBuffer::Lines lines( sourceCode.begin(), sourceCode.end() ); + + if ( scanResult != std::char_traits::eof() ){ + auto lineIt = lines.begin(); + + uint32_t currentLine = 1; /* Line numbers start counting at 1 */ + std::string currentFilename = fileName; + std::string lastFilename = fileName; + + while ( lineIt != lines.end() ){ + + // Check for lines inserted by the preprocessor which hold line numbers for included files + // Such lines have the pattern: '#line 21 "path/to/include.frag"' (without single quotation marks) + auto wasLineMarker = checkForLineNumberModifier( cref( lineIt.asString() ), ref( currentLine ), ref( currentFilename ), ref( lastFilename ) ); + + if ( 0 == strcmp( errorFileName.c_str(), currentFilename.c_str() ) ){ + if ( currentLine >= lineNumber - 3 ){ + ostringstream sourceContext; + const auto shaderSourceCodeLine = wasLineMarker ? "#include \"" + lastFilename + "\"" : lineIt.asString(); + + if ( currentLine == lineNumber ){ + sourceContext << of::utils::setConsoleColor( of::utils::ConsoleColor::eBrightCyan ); + } + + sourceContext << std::right << std::setw( 4 ) << currentLine << " | " << shaderSourceCodeLine; + + if ( currentLine == lineNumber ){ + sourceContext << of::utils::resetConsoleColor(); + } + + ofLogError() << sourceContext.str(); + } + + if ( currentLine >= lineNumber + 2 ){ + ofLogError(); // add empty for better readability + break; + } + } + ++lineIt; + ++currentLine; + } + } +}; + +// ---------------------------------------------------------------------- + +inline bool of::vk::Shader::compileGLSLtoSpirV( + const::vk::ShaderStageFlagBits shaderStage, + const std::string & sourceText, + std::string fileName, + std::vector& spirCode, + const std::map& defines_ +){ + + shaderc_shader_kind shaderType = getShaderCKind( shaderStage ); + + shaderc::Compiler compiler; + shaderc::CompileOptions options; + + // Set any #defines requested + for ( auto& d : defines_ ){ + options.AddMacroDefinition( d.first, d.second ); // Like -DMY_DEFINE=1 + } + + // Create a temporary callback object which deals with include preprocessor directives + options.SetIncluder( std::make_unique() ); + + auto preprocessorResult = compiler.PreprocessGlsl( sourceText, shaderType, fileName.c_str(), options ); + + std::vector sourceCode( preprocessorResult.cbegin(), preprocessorResult.cend() ); + + if ( preprocessorResult.GetCompilationStatus() != shaderc_compilation_status_success ){ + auto msg = preprocessorResult.GetErrorMessage(); + printError( fileName, msg , sourceCode ); + return false; + } + + auto module = compiler.CompileGlslToSpv( sourceCode.data(), sourceCode.size(), shaderType, fileName.c_str(), "main", options ); + + if ( module.GetCompilationStatus() != shaderc_compilation_status_success ){ + auto msg = module.GetErrorMessage(); + printError( fileName, msg, sourceCode ); + return false; + } else { + spirCode.clear(); + spirCode.assign( module.cbegin(), module.cend() ); + return true; + } +} + +// ---------------------------------------------------------------------- + +const std::string& of::vk::Shader::getName(){ + return mSettings.name; +} + +// ---------------------------------------------------------------------- + +bool of::vk::Shader::getSpirV( const ::vk::ShaderStageFlagBits shaderStage, Source& shaderSource ){ + + bool success = true; + + switch ( shaderSource.mType ){ + case Source::Type::eCode: + // nothing to do + break; + case Source::Type::eFilePath: + { + auto f = ofFile( shaderSource.filePath ); + + if ( !f.exists() ){ + ofLogFatalError() << "Shader file not found: " << shaderSource.filePath; + success = false; + break; + } + + // ---------| invariant: File exists. + + if ( mSettings.name == "" && shaderStage == ::vk::ShaderStageFlagBits::eVertex ){ + // if name has not been set explicitly, infer shader name from + // baseName of vertex shader file. + const_cast(mSettings.name) = f.getBaseName(); + } + + auto fExt = f.getExtension(); + ofBuffer fileBuf = ofBufferFromFile( shaderSource.filePath, true ); + + if ( fExt == "spv" ){ + // File is precompiled SPIR-V file + ofLogNotice() << "Loading SPIR-V shader code: " << shaderSource.filePath; + auto a = fileBuf.getData(); + shaderSource.spirvCode.assign( + reinterpret_cast( fileBuf.getData() ), + reinterpret_cast( fileBuf.getData() ) + fileBuf.size() / sizeof( uint32_t ) + ); + success = true; + break; + } + + // ----------| invariant: File does not have ".spv" extension + + success = compileGLSLtoSpirV( shaderStage, fileBuf.getText(), shaderSource.filePath.string(), shaderSource.spirvCode, shaderSource.defines); + if ( success && mSettings.printDebugInfo ){ + ofLogNotice() << "OK \tShader compile: " << shaderSource.filePath.string(); + } + break; + } + case Source::Type::eGLSLSourceInline: + { + std::string sourceText = shaderSource.glslSourceInline; + success = compileGLSLtoSpirV( shaderStage, sourceText, getName() + " (Inline GLSL)", shaderSource.spirvCode, shaderSource.defines); + if ( success && mSettings.printDebugInfo ){ + ofLogNotice() << "OK \tShader compile: [" << to_string(shaderStage) << "] " << getName() + " (Inline GLSL)"; + } + break; + } + default: + break; + } + + return success; +} + +// ---------------------------------------------------------------------- + +void of::vk::Shader::createVkShaderModule( const ::vk::ShaderStageFlagBits shaderType, const std::vector &spirCode ){ + + ::vk::ShaderModuleCreateInfo shaderModuleCreateInfo; + shaderModuleCreateInfo + .setFlags( ::vk::ShaderModuleCreateFlagBits() ) + .setCodeSize( spirCode.size() * sizeof(uint32_t)) + .setPCode( spirCode.data() ) + ; + + ::vk::ShaderModule module = mSettings.device.createShaderModule( shaderModuleCreateInfo ); + + auto tmpShaderStage = std::shared_ptr( new ShaderStage, [device = mSettings.device](ShaderStage* lhs){ + device.destroyShaderModule( lhs->module ); + delete lhs; + } ); + + tmpShaderStage->module = module; + + tmpShaderStage->createInfo = ::vk::PipelineShaderStageCreateInfo(); + tmpShaderStage->createInfo + .setStage( shaderType ) + .setModule( tmpShaderStage->module ) + .setPName( "main" ) + .setPSpecializationInfo( nullptr ) + ; + + mShaderStages[shaderType] = std::move( tmpShaderStage ); +} + +// ---------------------------------------------------------------------- + +void of::vk::Shader::reflect( + const std::map<::vk::ShaderStageFlagBits, std::shared_ptr>& compilers, + VertexInfo& vertexInfo +){ + // storage for reflected information about UBOs + + mUniforms.clear(); + mUboMembers.clear(); + + // for all shader stages + for ( auto &c : compilers ){ + + auto & compiler = *c.second; + auto & shaderStage = c.first; + + // ! TODO: process texture samplers + // This: http://gpuopen.com/wp-content/uploads/2016/03/VulkanFastPaths.pdf + // suggests one fast path is to bind all (!) textures into ONE DescriptorSet / binding + // as an array of textures, and then use pushConstants to fetch the index + // into the array for the texture we want for this particular draw. + // This would mean to create one descriptor per texture and to bind all these + // texture descriptors to one binding - and to one descriptorset. + + // --- uniform buffers --- + reflectUBOs( compiler, shaderStage ); + + // --- samplers + reflectSamplers( compiler, shaderStage ); + + reflectStorageBuffers(compiler, shaderStage); + + // --- vertex inputs --- + if ( shaderStage == ::vk::ShaderStageFlagBits::eVertex ){ + + if ( mSettings.vertexInfo.get() == nullptr ){ + // we only reflect vertex inputs if they haven't been set externally. + reflectVertexInputs( compiler, vertexInfo ); + } else{ + vertexInfo = *mSettings.vertexInfo; + } + + ::vk::PipelineVertexInputStateCreateInfo vertexInputStateCreateInfo = ::vk::PipelineVertexInputStateCreateInfo(); + vertexInputStateCreateInfo + .setVertexBindingDescriptionCount( vertexInfo.bindingDescription.size() ) + .setPVertexBindingDescriptions( vertexInfo.bindingDescription.data() ) + .setVertexAttributeDescriptionCount( vertexInfo.attribute.size() ) + .setPVertexAttributeDescriptions( vertexInfo.attribute.data() ) + ; + + vertexInfo.vi = std::move( vertexInputStateCreateInfo ); + } + + } + + mAttributeBindingNumbers.clear(); + // Create lookup table attribute name -> attibute binding number + // Note that multiple locations may share the same binding. + // Attribute binding number specifies which bound buffer to read data from for this particular attribute + for ( size_t i = 0; i != mVertexInfo.attributeNames.size(); ++i ){ + // assume attributeNames are sorted by location + mAttributeBindingNumbers[mVertexInfo.attributeNames[i]] = mVertexInfo.attribute[i].binding; + } + + + // reserve storage for dynamic uniform data for each uniform entry + // over all sets - then build up a list of ubos. + for ( const auto & uniformPair : mUniforms ){ + const auto & uniform = uniformPair.second; + + for ( const auto & uniformMemberPair : uniform.uboRange.subranges ){ + // add with combined name - this should always work + mUboMembers.insert( { uniformPair.first + "." + uniformMemberPair.first ,uniformMemberPair.second } ); + // add only with member name - this might work, but if members share the same name, we're in trouble. + mUboMembers.insert( { uniformMemberPair.first ,uniformMemberPair.second } ); + } + } + +} + +// ---------------------------------------------------------------------- + +size_t calcMaxRange(){ + of::vk::UniformId_t uniformT; + uniformT.dataRange = ~( 0ULL ); + return uniformT.dataRange; +} + +// ---------------------------------------------------------------------- + +bool of::vk::Shader::reflectUBOs( const spirv_cross::Compiler & compiler, const ::vk::ShaderStageFlagBits & shaderStage ){ + + static const size_t maxRange = calcMaxRange(); + + auto uniformBuffers = compiler.get_shader_resources().uniform_buffers; + + for ( const auto & ubo : uniformBuffers ){ + + Uniform_t tmpUniform; + + tmpUniform.name = ubo.name; + + tmpUniform.uboRange.storageSize = compiler.get_declared_struct_size( compiler.get_type( ubo.type_id ) ); + + if ( tmpUniform.uboRange.storageSize > maxRange ){ + ; + ofLogWarning() << of::utils::setConsoleColor( of::utils::ConsoleColor::eBrightYellow ) + << "Ubo '" << ubo.name << "' is too large. Consider splitting it up. Size: " << tmpUniform.uboRange.storageSize + << of::utils::resetConsoleColor(); + } + + + tmpUniform.layoutBinding + .setDescriptorCount( 1 ) /* Must be 1 for ubo bindings, as arrays of ubos are not allowed */ + .setDescriptorType( ::vk::DescriptorType::eUniformBufferDynamic ) /* All our uniform buffer are dynamic */ + .setStageFlags( shaderStage ) + ; + + getSetAndBindingNumber( compiler, ubo, tmpUniform.setNumber, tmpUniform.layoutBinding.binding); + + auto bufferRanges = compiler.get_active_buffer_ranges( ubo.id ); + + for ( const auto &r : bufferRanges ){ + // Note that SpirV-Cross will only tell us the ranges of *actually used* members within an UBO. + // By merging the ranges later, we effectively also create aliases for member names which are + // not consistently named the same. + auto memberName = compiler.get_member_name( ubo.base_type_id, r.index ); + tmpUniform.uboRange.subranges[memberName] = { tmpUniform.setNumber, tmpUniform.layoutBinding.binding, (uint32_t)r.offset, (uint32_t)r.range }; + } + + // Let's see if an uniform buffer with this fingerprint has already been seen. + // If yes, it would already be in uniformStore. + + auto insertion = mUniforms.insert( { ubo.name, tmpUniform } ); + + if (insertion.second == false ){ + // Uniform with this key already existed, nothing was inserted. + + auto & storedUniform = insertion.first->second; + + if ( storedUniform.uboRange.storageSize != tmpUniform.uboRange.storageSize ){ + + ofLogWarning() + << of::utils::setConsoleColor( of::utils::ConsoleColor::eBrightRed ) + << "Ubo: '" << ubo.name << "' re-defined with incompatible storage size." + << of::utils::resetConsoleColor(); + + // !TODO: try to recover. + return false; + } else if ( storedUniform.setNumber != tmpUniform.setNumber + || storedUniform.layoutBinding.binding != tmpUniform.layoutBinding.binding ){ + + ofLogWarning() + << of::utils::setConsoleColor( of::utils::ConsoleColor::eBrightYellow ) + << "Ubo: '" << ubo.name << "' re-defined with inconsistent set/binding numbers." + << of::utils::resetConsoleColor(); + } else { + // Merge stage flags + storedUniform.layoutBinding.stageFlags |= tmpUniform.layoutBinding.stageFlags; + // Merge memberRanges + ostringstream overlapMsg; + if ( checkMemberRangesOverlap( storedUniform.uboRange.subranges, tmpUniform.uboRange.subranges, overlapMsg ) ){ + + // member ranges overlap: print diagnostic message + ofLogWarning() + << of::utils::setConsoleColor( of::utils::ConsoleColor::eBrightYellow) + << "Inconsistency found parsing UBO: '" << ubo.name << "': " << std::endl << overlapMsg.str() + << of::utils::resetConsoleColor(); + } + // insert any new subranges if necesary. + storedUniform.uboRange.subranges.insert( tmpUniform.uboRange.subranges.begin(), tmpUniform.uboRange.subranges.end() ); + } + } + + } // end: for all uniform buffers + + + return true; +} + +// ---------------------------------------------------------------------- + +bool of::vk::Shader::reflectStorageBuffers( const spirv_cross::Compiler & compiler, const ::vk::ShaderStageFlagBits & shaderStage ){ + + static const size_t maxRange = calcMaxRange(); + + auto storageBuffers = compiler.get_shader_resources().storage_buffers; + + for ( const auto & buffer : storageBuffers){ + + Uniform_t tmpUniform; + + tmpUniform.name = buffer.name; + tmpUniform.uboRange.storageSize = compiler.get_declared_struct_size( compiler.get_type( buffer.type_id ) ); + + if ( tmpUniform.uboRange.storageSize > maxRange ){ + + ofLogWarning() << of::utils::setConsoleColor( of::utils::ConsoleColor::eBrightYellow ) + << "Ubo '" << buffer.name << "' is too large. Consider splitting it up. Size: " << tmpUniform.uboRange.storageSize + << of::utils::resetConsoleColor(); + } + + tmpUniform.layoutBinding + .setDescriptorCount( 1 ) /* Must be 1 for ubo bindings, as arrays of ubos are not allowed */ + .setDescriptorType( ::vk::DescriptorType::eStorageBufferDynamic ) /* All our storage buffers are dynamic */ + .setStageFlags( shaderStage ) + ; + + getSetAndBindingNumber( compiler, buffer, tmpUniform.setNumber, tmpUniform.layoutBinding.binding ); + + auto bufferRanges = compiler.get_active_buffer_ranges( buffer.id ); + + for ( const auto &r : bufferRanges ){ + auto memberName = compiler.get_member_name( buffer.base_type_id, r.index ); + tmpUniform.uboRange.subranges[memberName] = { tmpUniform.setNumber, tmpUniform.layoutBinding.binding, (uint32_t)r.offset, (uint32_t)r.range }; + } + + // Let's see if an uniform buffer with this fingerprint has already been seen. + // If yes, it would already be in uniformStore. + + auto insertion = mUniforms.insert( { buffer.name, tmpUniform } ); + if ( insertion.second == false ){ + + auto & storedUniform = insertion.first->second; + // Uniform with this key already existed, nothing was inserted. + if ( storedUniform.setNumber != tmpUniform.setNumber + || storedUniform.layoutBinding.binding != tmpUniform.layoutBinding.binding ){ + + ofLogWarning() + << of::utils::setConsoleColor( of::utils::ConsoleColor::eBrightYellow ) + << "Buffer: '" << buffer.name << "' re-defined with inconsistent set/binding numbers." + << of::utils::resetConsoleColor(); + } else{ + // Merge stage flags + storedUniform.layoutBinding.stageFlags |= tmpUniform.layoutBinding.stageFlags; + // insert any new subranges if necesary. + storedUniform.uboRange.subranges.insert( tmpUniform.uboRange.subranges.begin(), tmpUniform.uboRange.subranges.end() ); + } + } + + } // end: for all uniform buffers + + + return true; +} +// ---------------------------------------------------------------------- + +bool of::vk::Shader::reflectSamplers( const spirv_cross::Compiler & compiler, const ::vk::ShaderStageFlagBits & shaderStage ){ + + auto sampledImages = compiler.get_shader_resources().sampled_images; + + for ( const auto & sampledImage : sampledImages){ + + Uniform_t tmpUniform; + tmpUniform.name = sampledImage.name; + tmpUniform.layoutBinding + .setDescriptorCount( 1 ) //!TODO: find out how to query array size + .setDescriptorType( ::vk::DescriptorType::eCombinedImageSampler ) + .setStageFlags( shaderStage ) + ; + + getSetAndBindingNumber( compiler, sampledImage, tmpUniform.setNumber, tmpUniform.layoutBinding.binding ); + + // Let's see if an uniform buffer with this fingerprint has already been seen. + // If yes, it would already be in uniformStore. + + auto insertion = mUniforms.insert( { sampledImage.name, tmpUniform } ); + + if ( insertion.second == false ){ + // uniform with this key already exists: check set and binding numbers are identical + auto & storedUniform = insertion.first->second; + // otherwise print a warning and return false. + if ( storedUniform.layoutBinding.binding != tmpUniform.layoutBinding.binding + || storedUniform.setNumber != tmpUniform.setNumber ){ + + ofLogWarning() << of::utils::setConsoleColor( of::utils::ConsoleColor::eBrightYellow ) + << "Combined image sampler: '" << sampledImage.name << "' is declared multiple times, but with inconsistent binding/set number." + << of::utils::resetConsoleColor(); + return false; + } else{ + // Merge stage flags + storedUniform.layoutBinding.stageFlags |= tmpUniform.layoutBinding.stageFlags; + // insert any new subranges if necesary. + storedUniform.uboRange.subranges.insert( tmpUniform.uboRange.subranges.begin(), tmpUniform.uboRange.subranges.end() ); + } + } + + } // end: for all uniform buffers + + return true; +} + +// ---------------------------------------------------------------------- + +bool of::vk::Shader::createSetLayouts(){ + + // Consolidate uniforms into descriptor sets + + + + if ( mUniforms.empty() ){ + // nothing to do. + return true; + } + + // map from descriptorSet to map of (sparse) bindings + map> uniformSetLayouts; + + // --------| invariant: there are uniforms to assign to descriptorsets. + + for ( const auto & uniform : mUniforms ){ + + const std::pair uniformBinding = { + uniform.second.layoutBinding.binding, + uniform.second, + }; + + // attempt to insert a fresh set + auto setInsertion = uniformSetLayouts.insert( { uniform.second.setNumber, { uniformBinding } } ); + + if ( setInsertion.second == false ){ + // if there was already a set at this position, append to this set + auto bindingInsertion = setInsertion.first->second.insert( uniformBinding ); + if ( bindingInsertion.second == false ){ + ofLogError() + << of::utils::setConsoleColor( of::utils::ConsoleColor::eBrightRed ) + << "Could not insert binding - it appears that there is already a binding a this position, set: " << uniform.second.setNumber + << ", binding number: " << uniform.second.layoutBinding.binding + << of::utils::setConsoleColor( of::utils::ConsoleColor::eDefault ); + return false; + } + } + } + + + // assert set numbers are not sparse. + if ( uniformSetLayouts.size() != ( uniformSetLayouts.rbegin()->first + 1 ) ){ + ofLogError() << of::utils::setConsoleColor( of::utils::ConsoleColor::eBrightRed ) + << "Descriptor sets may not be sparse" + << of::utils::setConsoleColor( of::utils::ConsoleColor::eDefault ); + return false; + } + + + // make sure that for each set, bindings are not sparse by adding placeholder uniforms + // into empty binding slots. + { + Uniform_t placeHolderUniform; + placeHolderUniform.layoutBinding.descriptorCount = 0; // a count of 0 marks this descriptor as being a placeholder. + + for ( auto & uniformSetLayout : uniformSetLayouts ){ + + const auto & setNumber = uniformSetLayout.first; + auto & bindings = uniformSetLayout.second; + + placeHolderUniform.setNumber = setNumber; + + if ( bindings.empty() ){ + continue; + } + + // Attempt to insert placeholder descriptors for each binding. + uint32_t last_binding_number = bindings.crbegin()->first; + placeHolderUniform.layoutBinding.stageFlags = bindings[last_binding_number].layoutBinding.stageFlags; + uint32_t bindingCount = last_binding_number + 1; + + for ( uint32_t i = 0; i != bindingCount; ++i ){ + placeHolderUniform.layoutBinding.binding = i; + auto insertionResult = bindings.insert( { i, placeHolderUniform } ); + if ( insertionResult.second == true ){ + + ofLogWarning() << of::utils::setConsoleColor( of::utils::ConsoleColor::eBrightYellow ) + << "Detected sparse bindings: gap at set: " << setNumber << ", binding: " << i << ". This could slow the GPU down." + << of::utils::resetConsoleColor(); + } + } + } + } + + // ---------| invariant: we should have a map of sets and each set should have bindings + // and both in ascending order. + + { + + // create temporary data storage object for uniforms + // and uniform dictionary + + mDescriptorSetData.clear(); + mUniformDictionary.clear(); + mUniformBindings.clear(); + + size_t setLayoutIndex = 0; + + for ( auto & setLayoutBindingsMapPair : uniformSetLayouts ){ + + const auto & setNumber = setLayoutBindingsMapPair.first; + const auto & setLayoutBindingsMap = setLayoutBindingsMapPair.second; + + DescriptorSetData_t tmpDescriptorSetData; + UniformId_t uniformId; + + auto & descriptors = tmpDescriptorSetData.descriptors; + + uniformId.setIndex = setLayoutIndex; + uniformId.descriptorIndex = 0; + uniformId.auxDataIndex = -1; + + std::vector uniformsPerSet; + + for ( auto & bindingsPair : setLayoutBindingsMap ){ + + const auto & bindingNumber = bindingsPair.first; + const auto & uniform = bindingsPair.second; + const auto & layoutBinding = uniform.layoutBinding; + + uniformId.dataOffset = 0; + uniformId.dataRange = 0; + + if ( layoutBinding.descriptorType == ::vk::DescriptorType::eUniformBufferDynamic ){ + + tmpDescriptorSetData.dynamicBindingOffsets.push_back( 0 ); + tmpDescriptorSetData.dynamicUboData.push_back( {} ); + + uniformId.auxDataIndex = tmpDescriptorSetData.dynamicUboData.size() - 1; + + // go through member ranges if any. + for ( const auto &subRangePair : uniform.uboRange.subranges ){ + + auto uboMemberUniformId = uniformId; + + const auto & memberName = subRangePair.first; + const auto & memberRange = subRangePair.second; + + uboMemberUniformId.dataOffset = memberRange.offset; + uboMemberUniformId.dataRange = memberRange.range; + + mUniformDictionary.insert( { uniform.name + '.' + memberName, uboMemberUniformId } ); + + auto insertionResult = mUniformDictionary.insert( { memberName, uboMemberUniformId } ); + + if ( insertionResult.second == false ){ + ofLogWarning() + << of::utils::setConsoleColor( of::utils::ConsoleColor::eBrightYellow ) + << "Uniform Ubo member name not uniqe: '" << memberName << "'." + << of::utils::resetConsoleColor(); + } + + } + + uniformId.dataOffset = 0; + uniformId.dataRange = uniform.uboRange.storageSize; + + // make space for data storage + tmpDescriptorSetData.dynamicUboData.back().resize( uniformId.dataRange ); + } + + if ( layoutBinding.descriptorType == ::vk::DescriptorType::eStorageBufferDynamic ){ + tmpDescriptorSetData.dynamicBindingOffsets.push_back( 0 ); + tmpDescriptorSetData.bufferAttachment.push_back( {} ); + uniformId.auxDataIndex = tmpDescriptorSetData.bufferAttachment.size() - 1; + + uniformId.dataRange = uniform.uboRange.storageSize; + } + + for ( uint32_t arrayIndex = 0; arrayIndex != layoutBinding.descriptorCount; ++arrayIndex ){ + + DescriptorSetData_t::DescriptorData_t descriptorData; + descriptorData.bindingNumber = layoutBinding.binding; + descriptorData.arrayIndex = arrayIndex; + descriptorData.type = layoutBinding.descriptorType; + + if ( layoutBinding.descriptorType == ::vk::DescriptorType::eCombinedImageSampler ){ + // store image attachment + tmpDescriptorSetData.imageAttachment.push_back( {} ); + uniformId.auxDataIndex = tmpDescriptorSetData.imageAttachment.size() - 1; + } + + descriptors.emplace_back( std::move( descriptorData ) ); + + // FIXME: what uniform arrays need to have a name that makes it possible to look them + // up - i fear with arrays of images for example, inserting with the same name will + // overwrite previous entry with lower index. + if ( arrayIndex == 0 ) { + mUniformDictionary.insert( { uniform.name, uniformId } ); + } + + mUniformDictionary.insert( { uniform.name + "[" + ofToString(arrayIndex) + "]", uniformId} ); + + if ( arrayIndex == 0 ){ + uniformsPerSet.push_back( uniformId ); + } + + uniformId.descriptorIndex++; + } + } + + mUniformBindings.emplace_back( std::move( uniformsPerSet ) ); + mDescriptorSetData.emplace_back( std::move( tmpDescriptorSetData ) ); + ++setLayoutIndex; + } + + + } + + // --------- + + // print out shader binding log, but only when compiled in debug mode. + if ( mSettings.printDebugInfo ){ + + std::ostringstream log; + log << "Shader Uniform Bindings: " << endl; + + for ( const auto & descriptorSetPair : uniformSetLayouts ){ + const auto & setId = descriptorSetPair.first; + const size_t indentLevel = 2; + + log << std::string( indentLevel, ' ' ) + << " Set " << std::setw( 2 ) << setId << ": " << std::endl; + + for ( const auto & bindingPair : descriptorSetPair.second ){ + const size_t indentLevel = 6; + + const auto & bindingNumber = bindingPair.first; + const auto & binding = bindingPair.second; + + log << std::string( indentLevel, ' ' ) + << std::setw( 2 ) << std::right << binding.layoutBinding.binding; + + if ( binding.layoutBinding.descriptorCount == 0 ){ + log << " - UNUSED - " << std::endl; + } else{ + log << "[" << std::setw( 3 ) << std::right << binding.layoutBinding.descriptorCount << "] : '" << binding.name << "'\t"; + } + + switch ( binding.layoutBinding.descriptorType ){ + case ::vk::DescriptorType::eUniformBufferDynamic: + log << "Dynamic "; + // fall through to uniformBuffer, as these two are similar. + case ::vk::DescriptorType::eUniformBuffer: + { + log << "UniformBuffer - "; + log << " Total Size : " << std::right << std::setw( 4 ) << binding.uboRange.storageSize << "B"; + log << std::endl; + + std::map< of::vk::Shader::UboMemberSubrange, std::string> reverseSubrangeMap; + + for ( const auto & subrangePair : binding.uboRange.subranges ){ + reverseSubrangeMap.insert( { subrangePair.second, subrangePair.first } ); + }; + + for ( const auto & subrangePair : reverseSubrangeMap ){ + const size_t indentLevel = 12; + + const auto & name = subrangePair.second; + const auto & subrange = subrangePair.first; + + log << std::string( indentLevel, ' ' ) + << "> " << std::setw( 40 ) << "'" + subrangePair.second + "'" + << ", offset: " << std::setw( 5 ) << std::right << subrange.offset << "B" + << ", size : " << std::setw( 5 ) << std::right << subrange.range << "B" + << std::endl; + } + } + break; + case ::vk::DescriptorType::eStorageBufferDynamic: + log << "Dynamic Storage Buffer - "; + log << " Total Size : " << std::right << std::setw( 4 ) << binding.uboRange.storageSize << "B"; + log << std::endl; + + break; + case ::vk::DescriptorType::eCombinedImageSampler: + log << "Combined Image Sampler"; + break; + default: + break; + } // end switch binding.layoutBinding.descriptorType + + log << std::endl; + } + } + + // Print Attribute Inputs + { + size_t numAttributes = mVertexInfo.attribute.size(); + + log << std::endl << "Attribute Inputs:" << std::endl; + + for ( size_t i = 0; i != numAttributes; ++i ){ + log + << "\t(location = " << std::setw(2) << mVertexInfo.attribute[i].location << ") : " + << "binding : " << std::setw( 2 ) << mVertexInfo.attribute[i].binding << " : " + << "offset : " << std::setw( 4 ) << mVertexInfo.attribute[i].offset << "B : " + << std::right << std::setw( 15 ) << ::vk::to_string(mVertexInfo.attribute[i].format) << " : " + << mVertexInfo.attributeNames[i] + << std::endl; + } + + } + + ofLogNotice() << log.str(); + } + + // --------- build VkDescriptorSetLayouts + + mDescriptorSetsInfo.clear(); + mDescriptorSetsInfo.reserve( uniformSetLayouts.size() ); + + mDescriptorSetLayoutKeys.clear(); + mDescriptorSetLayoutKeys.reserve( uniformSetLayouts.size() ); + + for (const auto & descriptorSet : uniformSetLayouts ){ + DescriptorSetLayoutInfo layoutInfo; + layoutInfo.bindings.reserve( descriptorSet.second.size() ); + + for ( const auto & binding : descriptorSet.second ){ + layoutInfo.bindings.emplace_back( std::move(binding.second.layoutBinding) ); + } + + static_assert( + +sizeof( ::vk::DescriptorSetLayoutBinding::binding ) + + sizeof( ::vk::DescriptorSetLayoutBinding::descriptorType ) + + sizeof( ::vk::DescriptorSetLayoutBinding::descriptorCount ) + + sizeof( ::vk::DescriptorSetLayoutBinding::stageFlags ) + + sizeof( ::vk::DescriptorSetLayoutBinding::pImmutableSamplers ) + == sizeof( ::vk::DescriptorSetLayoutBinding ) + , "DescriptorSetLayoutBindings is not tightly packed." ); + + layoutInfo.hash = SpookyHash::Hash64(layoutInfo.bindings.data(),layoutInfo.bindings.size() * sizeof( ::vk::DescriptorSetLayoutBinding ), 0); + + mDescriptorSetLayoutKeys.push_back( layoutInfo.hash ); + mDescriptorSetsInfo.emplace_back( std::move( layoutInfo ) ); + } + + // -------| invariant: mDescriptorSetInfo contains information for each descriptorset. + + mDescriptorSetLayouts.clear(); + mDescriptorSetLayouts.reserve( mDescriptorSetsInfo.size() ); + + for ( auto & descriptorSetInfo : mDescriptorSetsInfo ){ + ::vk::DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo; + descriptorSetLayoutCreateInfo + .setBindingCount( descriptorSetInfo.bindings.size() ) + .setPBindings( descriptorSetInfo.bindings.data() ) + ; + + // Create the auto-deleter + std::shared_ptr<::vk::DescriptorSetLayout> vkDescriptorSetLayout = + std::shared_ptr<::vk::DescriptorSetLayout>( new ::vk::DescriptorSetLayout, [device = mSettings.device]( ::vk::DescriptorSetLayout* rhs ){ + if ( *rhs ){ + ofLog() << "destroy descriptorSetLayout: " << std::hex << *rhs; + // FIXME: THIS DOES CREATE SUBTLE BUGS, if deleter gets called whilst DescriptorSetLayout is still used by in-flight frame. + // device.destroyDescriptorSetLayout( *rhs ); + } + delete rhs; + } ); + + // create new descriptorSetLayout + *vkDescriptorSetLayout = mSettings.device.createDescriptorSetLayout( descriptorSetLayoutCreateInfo ); + + // store new descriptorSetLayout + mDescriptorSetLayouts.emplace_back( std::move( vkDescriptorSetLayout ) ); + } + + return true; +} +// ---------------------------------------------------------------------- + +void of::vk::Shader::getSetAndBindingNumber( const spirv_cross::Compiler & compiler, const spirv_cross::Resource & resource, uint32_t &descriptor_set, uint32_t &bindingNumber ){ + // see what kind of decorations this resource has + uint64_t decorationMask = compiler.get_decoration_mask( resource.id ); + + if ( ( 1ull << spv::DecorationDescriptorSet ) & decorationMask ){ + descriptor_set = compiler.get_decoration( resource.id, spv::DecorationDescriptorSet ); + } else{ + // If undefined, set descriptor set id to 0. This is conformant with: + // https://www.khronos.org/registry/vulkan/specs/misc/GL_KHR_vulkan_glsl.txt + descriptor_set = 0; + ofLogWarning() + << "Warning: Shader uniform " << resource.name << "does not specify set id, and will " << endl + << "therefore be mapped to set 0 - this could have unintended consequences."; + } + + if ( ( 1ull << spv::DecorationBinding ) & decorationMask ){ + bindingNumber = compiler.get_decoration( resource.id, spv::DecorationBinding ); + } else{ + ofLogWarning() << "Shader uniform" << resource.name << "does not specify binding number."; + } +} + +// ---------------------------------------------------------------------- + +void of::vk::Shader::reflectVertexInputs(const spirv_cross::Compiler & compiler, of::vk::Shader::VertexInfo& vertexInfo ){ + const auto shaderResources = compiler.get_shader_resources(); + + vertexInfo.attribute.resize( shaderResources.stage_inputs.size() ); + vertexInfo.bindingDescription.resize( shaderResources.stage_inputs.size() ); + vertexInfo.attributeNames.resize( shaderResources.stage_inputs.size() ); + + for ( uint32_t i = 0; i != shaderResources.stage_inputs.size(); ++i ){ + + auto & attributeInput = shaderResources.stage_inputs[i]; + auto attributeType = compiler.get_type( attributeInput.type_id ); + + uint32_t location = i; // shader location qualifier mapped to binding number + + if ( ( 1ull << spv::DecorationLocation ) & compiler.get_decoration_mask( attributeInput.id ) ){ + location = compiler.get_decoration( attributeInput.id, spv::DecorationLocation ); + } + + // TODO: figure out what to do if attribute bindings are sparse, i.e. the number of locations + // is less than the highest location reflected from DecorationLocation + + vertexInfo.attributeNames[location] = attributeInput.name; + + // Binding Description: Describe how to read data from buffer based on binding number + vertexInfo.bindingDescription[location].binding = location; // which binding number we are describing + vertexInfo.bindingDescription[location].stride = ( attributeType.width / 8 ) * attributeType.vecsize * attributeType.columns; + vertexInfo.bindingDescription[location].inputRate = ::vk::VertexInputRate::eVertex; + + + // Attribute description: Map shader location to pipeline binding number + vertexInfo.attribute[location].location = location; // .location == which shader attribute location + vertexInfo.attribute[location].binding = location; // .binding == pipeline binding number == which index of bound buffer where data is read from + vertexInfo.attribute[location].offset = 0; //TODO: allow interleaved vertex inputs + + switch ( attributeType.vecsize ){ + case 2: + vertexInfo.attribute[location].format = ::vk::Format::eR32G32Sfloat; // 2-part float + break; + case 3: + vertexInfo.attribute[location].format = ::vk::Format::eR32G32B32Sfloat; // 3-part float + break; + case 4: + vertexInfo.attribute[location].format = ::vk::Format::eR32G32B32A32Sfloat; // 4-part float + break; + default: + ofLogWarning() + << of::utils::setConsoleColor( of::utils::ConsoleColor::eBrightYellow ) + << "Could not determine vertex attribute type for: " << attributeInput.name + << of::utils::resetConsoleColor(); + break; + } + } + + +} + +// ---------------------------------------------------------------------- + +void of::vk::Shader::createVkPipelineLayout() { + + std::vector<::vk::DescriptorSetLayout> vkLayouts; + vkLayouts.reserve( mDescriptorSetLayouts.size() ); + + for ( const auto &layout : mDescriptorSetLayouts ){ + vkLayouts.push_back(*layout); + } + + auto pipelineInfo = ::vk::PipelineLayoutCreateInfo(); + pipelineInfo + .setSetLayoutCount( vkLayouts.size()) + .setPSetLayouts( vkLayouts.data() ) + .setPushConstantRangeCount( 0 ) + .setPPushConstantRanges( nullptr ) + ; + + + mPipelineLayout = std::shared_ptr<::vk::PipelineLayout>( new ::vk::PipelineLayout( mSettings.device.createPipelineLayout( pipelineInfo ) ), + [device = mSettings.device]( ::vk::PipelineLayout* rhs ){ + if ( rhs ){ + // FIXME: THIS DOES CREATE SUBTLE BUGS, if deleter gets called whilst PipelineLayout is still used by in-flight frame. + + ofLog() << "destroy pipelineLayout" << std::hex << *rhs; + // device.destroyPipelineLayout( *rhs ); + } + delete rhs; + } ); + +} + + +// ---------------------------------------------------------------------- +// Check whether member ranges within an UBO overlap +// Should this be the case, there is a good chance that the +// Ubo layout was inconsistently defined across shaders or +// shader stages, or that there was a typo in an UBO declaration. +bool of::vk::Shader::checkMemberRangesOverlap( + const std::map& lhs, + const std::map& rhs, + std::ostringstream & errorMsg ) { + + // Check whether member ranges overlap. + // + // 0. combine member ranges eagerly + // 1. sort member ranges by start + // 2. for each sorted member range + // 2.0 move to next if current member is exact duplicate of last member [perfect match, that's what we want.] + // 2.1 check if current member offset == last member offset [overlap because start at same place] + // 2.1 check if (last member offset + last member range) > current member offset [overlap because current starts inside last] + + bool overlap = false; + + if ( rhs.empty() ){ + // impossible that there might be a conflict if there is no second set to compare with. + return false; + } + + std::vector> ranges; + ranges.insert( ranges.begin(), lhs.begin(), lhs.end() ); + ranges.insert( ranges.begin(), rhs.begin(), rhs.end() ); + + std::sort( ranges.begin(), ranges.end(), []( const std::pair & lhs, + std::pair&rhs )->bool{ + return lhs.second.offset < rhs.second.offset; + } ); + + auto lastRangeIt = ranges.begin(); + for ( auto rangeIt = ++ranges.begin(); rangeIt != ranges.end(); lastRangeIt = rangeIt++ ){ + + if ( rangeIt->first == lastRangeIt->first + && rangeIt->second.offset == lastRangeIt->second.offset + && rangeIt->second.range == lastRangeIt->second.range + ){ + continue; + } + + bool overlapStart = false; + bool overlapRange = false; + + if ( rangeIt->second.offset == lastRangeIt->second.offset ){ + overlap = overlapStart = true; + } + + if ( ( lastRangeIt->second.offset + lastRangeIt->second.range ) > rangeIt->second.offset ){ + overlap = overlapRange = true; + } + + if ( overlapStart || overlapRange ){ + errorMsg << "Range for UBO Member Names: '" << rangeIt->first << "' and '" << lastRangeIt->first << "' overlap."; + if ( rangeIt->second.range == lastRangeIt->second.range ){ + errorMsg << "\nCheck for a possible typo in this UBO member name."; + } else{ + errorMsg << "\nCheck whether the elements within this UBO are laid out consistently over all shaders that use it within this Context."; + } + } + + } + + return overlap; +} + diff --git a/libs/openFrameworks/vk/Shader.h b/libs/openFrameworks/vk/Shader.h new file mode 100644 index 00000000000..d7616e76842 --- /dev/null +++ b/libs/openFrameworks/vk/Shader.h @@ -0,0 +1,479 @@ +#pragma once +#include +#include +#include "vulkan/vulkan.hpp" +#include "vk/spirv-cross/include/spirv_cross.hpp" +#include "vk/HelperTypes.h" + +// The smallest unit we may bind in Vulkan are DescriptorSets. +// +// Each set has its own namespace for bindings. Bindings may be sparse. +// +// If no set is specified explicitly in the shader, set 0 is used. +// +// A DescriptorSet is described by a DescriptorSetLayout. +// +// A DescriptorSetLayout is a table of DescriptorSetLayoutBindings +// +// A DescriptorSetLayoutBinding describes the type of descriptor and its count- +// if the count is >1, this DSLB represents an +// array of descriptors of the same type. +// +// An ordered array of DescriptorSetLayout is used to describe a PipelineLayout, +// it shows which DescriptorSets in sequence describe the inputs for a pipeline. +// +// set 0 +// |- binding 0 - descriptor 0 +// |- binding 1 - decriptors[] 1,2,3 +// |- +// |- binding 3 - descriptor 4 +// +// set 1 +// |- binding 0 +// |- binding 1 +// +// There does not seem to be a maximum number of descriptors that we can allocate, +// it is limited by GPU memory. On AMD/Radeon, a descriptor consumes at most 32 bytes, +// so you can have 32k descriptors per MB of GPU memory. On AMD, there is a privileged +// 256 MB of memory that is directly accessible through the CPU - on NVIDIA, this +// chunk is possibly larger. This is where Descriptors are stored. +// +// Descriptors are allocated from DescriptorPools. If you use +// VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, this means the pool uses a dynamic +// allocator, which brings the risk of fragmentation. If you set up the pool so that it +// only supports RESET, then this pool will have a linear allocator, memory will be pre- +// allocated in a big chunk, and allocating and resetting is effectively updating an +// offset, similar to what of::vk::Allocator does. +// +// Also, vkWriteDescriptorSet writes directly to GPU memory. +// +// There is a maximum number of DescriptorSets and Descriptors that can be bound +// at the same time. +// +// Here is some more information about the Vulkan binding model: +// https://developer.nvidia.com/vulkan-shader-resource-binding +// +// Some information on Descriptor Sets and fast paths +// http://gpuopen.com/wp-content/uploads/2016/03/VulkanFastPaths.pdf +// +// +// +// +// Here is a diagram on how vertex attribute binding works, based on vkspec 1.0, pp.389 +// +// +// SHADER | Pipeline setup PIPELINE | COMMAND BUFFER | BUFFERS (GPU MEMORY) +// ---------------------------------------------------------------|--------------------------------------------|-----------------------|-------------------|--------------------- +// shader variable => associated with => input attribute number| association on per-pipeline basis | vertex input bindings | association on a per-draw basis +// in GLSL, set through | when defining the pipeline with | | when binding buffers using +// "location" qualifier | "VkPipelineVertexInputStateCreateInfo" | "binding number" | "vkCmdBindVertexBuffers" +// | | | | +// | attributeDescription -> bindingDescription | | | +// | "location" -> "binding" | | | +// vec3 inPos => (location = 0) | --------------------. .------------------ | [0] ----------------- | ------. .------ | buffer A +// | \/ | | \/ | +// | /\ | | /\ | +// vec3 inNormal => (location = 1) | --------------------' '------------------ | [1] ----------------- | ------' '------ | buffer B +// + + +namespace of{ +namespace vk{ + + + +// ---------- + + +class Shader +{ +public: + + struct DescriptorSetLayoutInfo + { + uint64_t hash; // hash for this descriptor set layout. + std::vector<::vk::DescriptorSetLayoutBinding> bindings; + }; + + struct VertexInfo + { + std::vector attributeNames; // attribute names sorted by location + std::vector<::vk::VertexInputBindingDescription> bindingDescription; // describes data input parameters for pipeline slots + std::vector<::vk::VertexInputAttributeDescription> attribute; // mapping of attribute locations to pipeline slots + ::vk::PipelineVertexInputStateCreateInfo vi; + }; + + struct UboMemberSubrange + { + uint32_t setNumber; + uint32_t bindingNumber; + uint32_t offset; + uint32_t range; + + friend + inline bool operator < ( UboMemberSubrange const & lhs, UboMemberSubrange const & rhs ){ + return lhs.offset < rhs.offset; + } + }; + + struct UboRange + { + uint32_t storageSize = 0; + std::map subranges; + }; + + struct Uniform_t + { + uint32_t setNumber = 0; + ::vk::DescriptorSetLayoutBinding layoutBinding; /* this contains binding number */ + + std::string name; + UboRange uboRange; + }; + + class Source + { + // Shader source may be one of: + // + Vector of uint32_t binary spir-v code + // + A filesystem path from where to load glsl/spv source + // + A string of glsl code + + std::vector spirvCode; + std::filesystem::path filePath; + std::string glslSourceInline; + + std::map defines; // passed to GLSL compiler like: "-DNAME=VALUE" + + enum class Type : uint8_t + { + eUndefined = 0, + eCode = 1, + eFilePath = 2, + eGLSLSourceInline = 3, // inline GLSL + } mType = Type::eUndefined; + + public: + + Source( std::vector code_ ) + : spirvCode( code_ ) + , mType( Type::eCode ){}; + + Source( std::filesystem::path path_ ) + : filePath( path_ ) + , mType( Type::eFilePath ){}; + + Source( const char* filePath ) + : filePath( filePath ) + , mType( Type::eFilePath ){}; + + Source( const std::string& glslSourceInline ) + : glslSourceInline( glslSourceInline ) + , mType( Type::eGLSLSourceInline ){}; + + const std::string getName() const{ + switch ( mType ){ + case Source::Type::eCode: + return "Inline SPIRV"; + case Source::Type::eFilePath: + return filePath.string(); + case Source::Type::eGLSLSourceInline: + return "Inline GLSL"; + default: + return ""; + } + }; + + // Default vertex shader forwards normals, and position in eye space, + // outputs transformed geometry to clip space + static const std::vector defaultShaderVert; + + // Draw geometry using globalColor + static const std::vector defaultShaderFragGlobalColor; + + // Draw geometry in color based on normals + static const std::vector defaultShaderFragNormalColor; + + friend class Shader; + }; + + struct Settings + { + bool printDebugInfo = false; + ::vk::Device device; + std::shared_ptr vertexInfo; // Set this if you want to override vertex info generated through shader reflection + mutable std::map<::vk::ShaderStageFlagBits, Source> sources; // specify source object for each shader stage - if source is of type eFilePath, file extension is '.spv' no compilation occurs, otherwise file is loaded and compiled using shaderc + std::string name; // Debug name for shader - by default and if possible this is set to name of vertex shader file, less extension + + Settings& setPrintDebugInfo( bool shouldPrint_ ){ + printDebugInfo = shouldPrint_; + return *this; + } + Settings& setDevice( const ::vk::Device& device_ ){ + device = device_; + return *this; + } + + Settings& setSource( ::vk::ShaderStageFlagBits shaderType_, const Source& src_ ){ + // We first erase at key position in case this source was set before + // in case there was no entry with this key, this will do nothing + sources.erase( shaderType_ ); + // We use emplace, as this uses a templated constructor, which will + // construct the element in place, allowing Shader::Source + // to find the most matching specialised constructor. + sources.emplace( shaderType_, src_ ); + return *this; + } + Settings& setVertexInfo( std::shared_ptr info_ ){ + vertexInfo = info_; + return *this; + } + Settings& setName( const std::string& name_ ){ + name = name_; + return *this; + } + Settings& clearSources(){ + sources.clear(); + return *this; + } + }; + +private: + + const Settings mSettings; + + // default template for descriptor set data + std::vector mDescriptorSetData; + // default keys into descriptor set data. + std::map mUniformDictionary; + + // keys for resources based on set-, binding number + std::vector> mUniformBindings; + + VertexInfo mVertexInfo; + + // map from uniform name to uniform data + std::map mUniforms; + + // lookup table from uniform name to storage info for dynamic ubos + std::map mUboMembers; + + // vector of descriptor set binding information (index is descriptor set number) + std::vector mDescriptorSetsInfo; + + // attribute binding numbers, indexed by attribute name + std::map mAttributeBindingNumbers; + + // vector of just the descriptor set layout keys - this needs to be kept in sync + // with mDescriptorSetsInfo + std::vector mDescriptorSetLayoutKeys; + + std::vector> mDescriptorSetLayouts; + + std::shared_ptr<::vk::PipelineLayout> mPipelineLayout; + + uint64_t mShaderHash = 0; + bool mShaderHashDirty = true; + + struct ShaderStage{ + ::vk::ShaderModule module; + ::vk::PipelineShaderStageCreateInfo createInfo; + }; + + std::map<::vk::ShaderStageFlagBits, std::shared_ptr> mShaderStages; + std::map<::vk::ShaderStageFlagBits, std::shared_ptr> mSpvCrossCompilers; + + // hashes for pre-compiled spirv + // we use this to find out if shader code has changed. + std::map<::vk::ShaderStageFlagBits, uint64_t> mSpvHash; + + // ---------------------------------------------------------------------- + // Derive bindings from shader reflection using SPIR-V Cross. + // we want to extract as much information out of the shader metadata as possible + // all this data helps us to create descriptors, and also to create layouts fit + // for our pipelines. + void reflect( const std::map<::vk::ShaderStageFlagBits, std::shared_ptr>& compilers, VertexInfo& vertexInfo ); + + static void reflectVertexInputs( const spirv_cross::Compiler & compiler, of::vk::Shader::VertexInfo& vertexInfo ); + + bool reflectUBOs( const spirv_cross::Compiler & compiler, const ::vk::ShaderStageFlagBits & shaderStage ); + bool reflectSamplers( const spirv_cross::Compiler & compiler, const ::vk::ShaderStageFlagBits & shaderStage); + bool reflectStorageBuffers( const spirv_cross::Compiler & compiler, const ::vk::ShaderStageFlagBits & shaderStage ); + + bool createSetLayouts(); + void createVkPipelineLayout(); + + // based on shader source type, read /compile either spirv or glsl file and fill vector of spirV words + bool getSpirV( const ::vk::ShaderStageFlagBits shaderType, of::vk::Shader::Source& shaderSource ); + + // find out if module is dirty + bool isSpirCodeDirty( const ::vk::ShaderStageFlagBits shaderStage, uint64_t spirvHash ); + + // create vkShader module from binary spirv code + void createVkShaderModule( const ::vk::ShaderStageFlagBits shaderType, const std::vector &spirCode); + + static bool checkMemberRangesOverlap( + const std::map& lhs, + const std::map& rhs, + std::ostringstream & errorMsg ); + + static void getSetAndBindingNumber( const spirv_cross::Compiler & compiler, const spirv_cross::Resource & resource, uint32_t &descriptor_set, uint32_t &bindingNumber ); + + + // Utility method for finding line number modifiers when compiling using shaderc + // line number modifiers are lines which the preprocessor inserts to mark line + // number offsets for included files. + static bool checkForLineNumberModifier( const std::string& line, uint32_t& lineNumber, std::string& currentFilename, std::string& lastFilename ); + + // Utility method for printing errors when compiling using shaderc + static void printError( const std::string& fileName, std::string& errorMessage, std::vector& sourceCode ); + +public: + + // ---------------------------------------------------------------------- + + // shader object needs to be initialised based on spir-v sources to be useful + Shader( const of::vk::Shader::Settings& settings_ ); + + // ---------------------------------------------------------------------- + + ~Shader() + { + mSpvCrossCompilers.clear(); + mShaderStages.clear(); + } + + // re-compile shader - this invalidates hashes only when shader has changed. + // returns true if new shader compiled successfully, otherwise false + bool compile(); + + // return shader stage information for pipeline creation + const std::vector<::vk::PipelineShaderStageCreateInfo> getShaderStageCreateInfo(); + + const std::vector & getDescriptorSetsInfo(); + + // return vertex input state create info + // this hold the layout and wiring of attribute inputs to vertex bindings + const ::vk::PipelineVertexInputStateCreateInfo& getVertexInputState(); + + // return pipeline layout reflected from this shader + const std::shared_ptr<::vk::PipelineLayout>& getPipelineLayout(); + + const std::vector & getDescriptorSetLayoutKeys() const; + + // get setLayout at set index + const std::shared_ptr<::vk::DescriptorSetLayout>& getDescriptorSetLayout( size_t setId ) const; + + // get all set layouts + const std::vector>& getDescriptorSetLayouts() const; + + // returns hash of spirv code over all shader shader stages + const uint64_t getShaderCodeHash(); + + //const std::map& getUniforms(); + + const std::vector & getAttributeNames(); + + bool getAttributeBinding( const std::string& name, size_t& index ) const; + + const VertexInfo& getVertexInfo(); + + const std::vector & getDescriptorSetData() const{ + return mDescriptorSetData; + }; + + const std::map& getUniformDictionary() const{ + return mUniformDictionary; + } + + const std::vector>& getUniformBindings() const { + return mUniformBindings; + }; + + // Compile source text and store result in vector of SPIR-V words + static bool compileGLSLtoSpirV( const ::vk::ShaderStageFlagBits shaderStage, const std::string & sourceText, std::string fileName, std::vector& spirCode, const std::map& defines_ = {}); + + // shader name is debug name for shader + const std::string& getName(); +}; + +// ---------------------------------------------------------------------- + +inline const std::vector<::vk::PipelineShaderStageCreateInfo> of::vk::Shader::getShaderStageCreateInfo(){ + + std::vector<::vk::PipelineShaderStageCreateInfo> stageInfo; + stageInfo.reserve( mShaderStages.size() ); + + for ( const auto& s : mShaderStages ){ + stageInfo.push_back( s.second->createInfo ); + } + + return stageInfo; +} + +// ---------------------------------------------------------------------- + +inline const std::vector& of::vk::Shader::getDescriptorSetsInfo(){ + return mDescriptorSetsInfo; +} + +// ---------------------------------------------------------------------- + +inline const ::vk::PipelineVertexInputStateCreateInfo & of::vk::Shader::getVertexInputState(){ + return mVertexInfo.vi; +} + +// ---------------------------------------------------------------------- + +inline const std::vector & of::vk::Shader::getAttributeNames(){ + return mVertexInfo.attributeNames; +} + +// ---------------------------------------------------------------------- + +inline bool of::vk::Shader::getAttributeBinding( const std::string &name, size_t &index ) const{ + auto result = mAttributeBindingNumbers.find( name ); + + if ( result == mAttributeBindingNumbers.end() ){ + return false; + } else{ + index = result->second; + return true; + }; +} + +// ---------------------------------------------------------------------- + +inline const of::vk::Shader::VertexInfo & of::vk::Shader::getVertexInfo(){ + return mVertexInfo; +} + +// ---------------------------------------------------------------------- + +inline const std::shared_ptr<::vk::PipelineLayout>& Shader::getPipelineLayout() { + if ( mPipelineLayout.get() == nullptr ) + createVkPipelineLayout(); + return mPipelineLayout; +} + +// ---------------------------------------------------------------------- + +inline const std::vector& Shader::getDescriptorSetLayoutKeys() const{ + return mDescriptorSetLayoutKeys; +} + +// ---------------------------------------------------------------------- + +inline const std::vector>& of::vk::Shader::getDescriptorSetLayouts() const{ + return mDescriptorSetLayouts; +} + +// ---------------------------------------------------------------------- + +inline const std::shared_ptr<::vk::DescriptorSetLayout>& of::vk::Shader::getDescriptorSetLayout( size_t setId ) const{ + return mDescriptorSetLayouts.at( setId ); +} + +// ---------------------------------------------------------------------- + +} // namespace vk +} // namespace of diff --git a/libs/openFrameworks/vk/Swapchain.cpp b/libs/openFrameworks/vk/Swapchain.cpp new file mode 100644 index 00000000000..1da67ba6e73 --- /dev/null +++ b/libs/openFrameworks/vk/Swapchain.cpp @@ -0,0 +1,315 @@ +#include "vk/Swapchain.h" +#include +#include +#include +#include "ofLog.h" + +using namespace std; +using namespace of::vk; + +// ---------------------------------------------------------------------- + +WsiSwapchain::WsiSwapchain( const WsiSwapchainSettings & settings_ ) + : mSettings( settings_ ){ +} + +// ---------------------------------------------------------------------- + +WsiSwapchain::~WsiSwapchain(){ + // It's imperative we clean up. + + for ( auto & b : mImages ){ + // Note that we only destroy the VkImageView, + // as the VkImage is owned by the swapchain mSwapchain + // and will get destroyed when destroying the swapchain + mDevice.destroyImageView( b.view ); + } + mImages.clear(); + + mDevice.destroySwapchainKHR( mVkSwapchain ); +} + +// ---------------------------------------------------------------------- + +void WsiSwapchain::setup() +{ + ::vk::Result err = ::vk::Result::eSuccess; + + // The surface in SwapchainSettings::windowSurface has been assigned by glfwwindow, through glfw, + // just before this setup() method was called. + querySurfaceCapabilities(); + + ::vk::SwapchainKHR oldSwapchain = mVkSwapchain; + + // Get physical device surface properties and formats + const ::vk::SurfaceCapabilitiesKHR & surfCaps = mSurfaceProperties.capabilities; + + // Get available present modes for physical device + const std::vector<::vk::PresentModeKHR>& presentModes = mSurfaceProperties.presentmodes; + + + // Either set or get the swapchain surface extents + ::vk::Extent2D swapchainExtent = {}; + + if ( surfCaps.currentExtent.width == -1 ){ + swapchainExtent.width = mSettings.width; + swapchainExtent.height = mSettings.height; + } else { + // set dimensions from surface extents if surface extents are available + swapchainExtent = surfCaps.currentExtent; + const_cast(mSettings.width) = surfCaps.currentExtent.width; + const_cast(mSettings.height) = surfCaps.currentExtent.height; + } + + // Prefer user-selected present mode, + // use guaranteed fallback mode (FIFO) if preferred mode couldn't be found. + ::vk::PresentModeKHR swapchainPresentMode = ::vk::PresentModeKHR::eFifo; + + bool presentModeSwitchSuccessful = false; + + for ( auto & p : presentModes ){ + if ( p == mSettings.presentMode ){ + swapchainPresentMode = p; + presentModeSwitchSuccessful = true; + break; + } + } + + if (!presentModeSwitchSuccessful){ + ofLogWarning() << "Could not switch to selected Swapchain Present Mode. Falling back to FIFO..."; + } + + // Write current present mode back to reference from parameter + // so caller can find out whether chosen present mode has been + // applied successfully. + const_cast<::vk::PresentModeKHR&>(mSettings.presentMode) = swapchainPresentMode; + + uint32_t requestedNumberOfSwapchainImages = std::max( surfCaps.minImageCount, mSettings.numSwapchainImages ); + if ( ( surfCaps.maxImageCount > 0 ) && ( requestedNumberOfSwapchainImages > surfCaps.maxImageCount ) ){ + requestedNumberOfSwapchainImages = surfCaps.maxImageCount; + } + + if ( mSettings.numSwapchainImages != requestedNumberOfSwapchainImages ){ + ofLogWarning() << "Swapchain: Number of swapchain images was adjusted to: " << requestedNumberOfSwapchainImages; + } + + // Write current value back to parameter reference so caller + // has a chance to check if values were applied correctly. + const_cast(mSettings.numSwapchainImages) = requestedNumberOfSwapchainImages; + + ::vk::SurfaceTransformFlagBitsKHR preTransform; + // Note: this will be interesting for mobile devices + // - if rotation and mirroring for the final output can + // be defined here. + + if ( surfCaps.supportedTransforms & ::vk::SurfaceTransformFlagBitsKHR::eIdentity){ + preTransform = ::vk::SurfaceTransformFlagBitsKHR::eIdentity; + } else{ + preTransform = surfCaps.currentTransform; + } + + ::vk::SwapchainCreateInfoKHR swapChainCreateInfo; + + swapChainCreateInfo + .setSurface ( mSettings.windowSurface ) + .setMinImageCount ( requestedNumberOfSwapchainImages) + .setImageFormat ( mWindowColorFormat.format) + .setImageColorSpace ( mWindowColorFormat.colorSpace) + .setImageExtent ( swapchainExtent ) + .setImageArrayLayers ( 1 ) + .setImageUsage ( ::vk::ImageUsageFlagBits::eColorAttachment ) + .setImageSharingMode ( ::vk::SharingMode::eExclusive ) + .setPreTransform ( preTransform ) + .setCompositeAlpha ( ::vk::CompositeAlphaFlagBitsKHR::eOpaque ) + .setPresentMode ( mSettings.presentMode ) + .setClipped ( VK_TRUE ) + .setOldSwapchain ( oldSwapchain ) + ; + + mVkSwapchain = mDevice.createSwapchainKHR( swapChainCreateInfo ); + + // If an existing swap chain is re-created, destroy the old swap chain + // This also cleans up all the presentable images + if ( oldSwapchain ){ + mDevice.destroySwapchainKHR( oldSwapchain ); + oldSwapchain = nullptr; + } + + std::vector<::vk::Image> swapchainImages = mDevice.getSwapchainImagesKHR( mVkSwapchain ); + mImageCount = swapchainImages.size(); + + + for ( auto&b : mImages ){ + // If there were any images available at all to iterate over, this means + // that the swapchain was re-created. + // This happens on window resize, for example. + // Therefore we have to destroy old ImageView object(s). + mDevice.destroyImageView( b.view ); + } + + mImages.resize( mImageCount ); + + for ( uint32_t i = 0; i < mImageCount; i++ ){ + + mImages[i].imageRef = swapchainImages[i]; + + ::vk::ImageSubresourceRange subresourceRange; + subresourceRange + .setAspectMask ( ::vk::ImageAspectFlagBits::eColor ) + .setBaseMipLevel ( 0 ) + .setLevelCount ( 1 ) + .setBaseArrayLayer ( 0 ) + .setLayerCount ( 1 ) + ; + + ::vk::ImageViewCreateInfo imageViewCreateInfo; + imageViewCreateInfo + .setImage ( mImages[i].imageRef ) + .setViewType ( ::vk::ImageViewType::e2D ) + .setFormat ( mWindowColorFormat.format ) + .setComponents ( ::vk::ComponentMapping() ) + .setSubresourceRange ( subresourceRange ) + ; + // create image view for color image + mImages[i].view = mDevice.createImageView( imageViewCreateInfo ); + + } + +} + +// Return current swapchain image width in pixels + +inline uint32_t WsiSwapchain::getWidth(){ + return mSettings.width; +} + +// ---------------------------------------------------------------------- +// Return current swapchain image height in pixels +inline uint32_t WsiSwapchain::getHeight(){ + return mSettings.height; +} + +// ---------------------------------------------------------------------- +// Change width and height in internal settings. +// Caution: this method requires a call to setup() to be applied, and is very costly. +void WsiSwapchain::changeExtent( uint32_t w, uint32_t h ){ + mSurfaceProperties.queried = false; + const_cast( mSettings.width ) = w; + const_cast( mSettings.height ) = h; +} + +// ---------------------------------------------------------------------- + +const ::vk::Format & WsiSwapchain::getColorFormat(){ + return mWindowColorFormat.format; +} + +// ---------------------------------------------------------------------- + +// Acquires the next available image in the swap chain and updates imageIndex with index to image +// Signals semaphorePresentComplete once image has been acquired +vk::Result WsiSwapchain::acquireNextImage( ::vk::Semaphore semaphorePresentComplete, uint32_t &imageIndex ){ + + // Timeout parameter 0 makes this call a non-blocking operation. + // see Vk Spec with extensions: C 2.3.6 + auto err = vkAcquireNextImageKHR( mDevice, mVkSwapchain, 0, semaphorePresentComplete, ( VkFence )nullptr, &imageIndex ); + + if ( err != VK_SUCCESS ){ + ofLogWarning() << "Swapchain image acquisition returned: " << err; + imageIndex = mImageIndex; + } + mImageIndex = imageIndex; + + return ::vk::Result(err); +} + +// ---------------------------------------------------------------------- + +vk::Result WsiSwapchain::queuePresent( ::vk::Queue queue, std::mutex & queueMutex, const std::vector<::vk::Semaphore>& waitSemaphores_ ){ + + // TODO: waitSemaphore is only needed if we wait for an operation from another queue to complete. + + ::vk::PresentInfoKHR presentInfo; + presentInfo + .setWaitSemaphoreCount( waitSemaphores_.size() ) + .setPWaitSemaphores ( waitSemaphores_.data() ) + .setSwapchainCount( 1 ) + .setPSwapchains( &mVkSwapchain ) + .setPImageIndices( &mImageIndex) + ; + + ::vk::Result result; + { + std::lock_guard lock{ queueMutex }; + try { + result = queue.presentKHR( presentInfo ); + } catch (::vk::SystemError & e) { + ofLog() << "swapchain did throw: " << e.what(); + } + } + + // each command wich begins with vkQueue... is appended to the end of the + // queue. this includes presenting. + return result; +} + +// ---------------------------------------------------------------------- +// return image by index +const ImageWithView & WsiSwapchain::getImage( size_t i ) const{ + return mImages[i]; +} + +// ---------------------------------------------------------------------- +// return number of swapchain images +const uint32_t WsiSwapchain::getImageCount() const{ + return mImageCount; +} + +// ---------------------------------------------------------------------- +// return last acquired buffer id +const uint32_t & WsiSwapchain::getCurrentImageIndex() const{ + return mImageIndex; +} + +// ---------------------------------------------------------------------- + +void WsiSwapchain::querySurfaceCapabilities(){ + + if ( mSurfaceProperties.queried == false ){ + + // we need to find out if the current physical device supports PRESENT + mRendererProperties.physicalDevice.getSurfaceSupportKHR( mRendererProperties.graphicsFamilyIndex, mSettings.windowSurface, &mSurfaceProperties.presentSupported ); + + // find out which color formats are supported + // Get list of supported surface formats + mSurfaceProperties.surfaceFormats = mRendererProperties.physicalDevice.getSurfaceFormatsKHR( mSettings.windowSurface ); + + mSurfaceProperties.capabilities = mRendererProperties.physicalDevice.getSurfaceCapabilitiesKHR( mSettings.windowSurface ); + mSurfaceProperties.presentmodes = mRendererProperties.physicalDevice.getSurfacePresentModesKHR( mSettings.windowSurface ); + + // If the surface format list only includes one entry with VK_FORMAT_UNDEFINED, + // there is no preferred format, so we assume VK_FORMAT_B8G8R8A8_UNORM + if ( ( mSurfaceProperties.surfaceFormats.size() == 1 ) && ( mSurfaceProperties.surfaceFormats[0].format == ::vk::Format::eUndefined ) ){ + mWindowColorFormat.format = ::vk::Format::eB8G8R8A8Unorm; + } else{ + // Always select the first available color format + // If you need a specific format (e.g. SRGB) you'd need to + // iterate over the list of available surface format and + // check for its presence + mWindowColorFormat.format = mSurfaceProperties.surfaceFormats[0].format; + } + mWindowColorFormat.colorSpace = mSurfaceProperties.surfaceFormats[0].colorSpace; + + // ofLog() << "Present supported: " << ( mSurfaceProperties.presentSupported ? "TRUE" : "FALSE" ); + mSurfaceProperties.queried = true; + } +} + +// ---------------------------------------------------------------------- + +void WsiSwapchain::setRendererProperties( const RendererProperties & rendererProperties_ ){ + mRendererProperties = rendererProperties_; +} + + + diff --git a/libs/openFrameworks/vk/Swapchain.h b/libs/openFrameworks/vk/Swapchain.h new file mode 100644 index 00000000000..e451f15e1d6 --- /dev/null +++ b/libs/openFrameworks/vk/Swapchain.h @@ -0,0 +1,158 @@ +#pragma once + +#include "vulkan/vulkan.hpp" +#include "vk/HelperTypes.h" + +#include + +namespace of{ +namespace vk{ + +// ---------------------------------------------------------------------- + +struct SwapchainSettings +{ + uint32_t width = 0; + uint32_t height = 0; + uint32_t numSwapchainImages = 0; +}; + +// ---------------------------------------------------------------------- + +struct WsiSwapchainSettings : public SwapchainSettings +{ + ::vk::PresentModeKHR presentMode = ::vk::PresentModeKHR::eFifo; + ::vk::SurfaceKHR windowSurface = nullptr; +}; + + +// ---------------------------------------------------------------------- + +// Todo: clarify this +// Image view for image which Swapchain donesn't own +// Owner of image is WSI +struct ImageWithView +{ + ::vk::Image imageRef = nullptr; // owned by SwapchainKHR, only referenced here + ::vk::ImageView view = nullptr; +}; + +// ---------------------------------------------------------------------- + +class Swapchain { +public: + + virtual void setRendererProperties( const of::vk::RendererProperties& rendererProperties_ ) = 0 ; + virtual void setup(){}; + + virtual ~Swapchain(){}; + + // Request an image index from the swapchain, so that we might render into it + // the image must be returned to the swapchain when done using queuePresent + // \note this might cause waiting. + // presentCompleteSemaphore will be signalled once image is ready to be rendered into. + virtual ::vk::Result acquireNextImage( ::vk::Semaphore presentCompleteSemaphore, uint32_t &imageIndex ) = 0; + + // Present the current image to the queue + // Waits with execution until all waitSemaphores have been signalled + virtual ::vk::Result queuePresent( ::vk::Queue queue, std::mutex & queueMutex,const std::vector<::vk::Semaphore>& waitSemaphores_ ) = 0; + + // return images vector + // virtual const std::vector & getImages() const = 0 ; + + // return image by index + virtual const ImageWithView& getImage( size_t i ) const = 0 ; + + // return number of swapchain images + virtual const uint32_t getImageCount() const = 0; + + // return last acquired buffer id + virtual const uint32_t & getCurrentImageIndex() const = 0; + + virtual const ::vk::Format& getColorFormat() = 0; + + // Return current swapchain image width in pixels + virtual uint32_t getWidth() = 0 ; + + // Return current swapchain image height in pixels + virtual uint32_t getHeight() = 0; + + // Change width and height in internal settings. + // Caution: this method requires a call to setup() to be applied, and is very costly. + virtual void changeExtent( uint32_t w, uint32_t h ) = 0; +}; + +// ---------------------------------------------------------------------- + +class WsiSwapchain : public Swapchain +{ + const WsiSwapchainSettings mSettings; + + uint32_t mImageCount = 0; + uint32_t mImageIndex = 0; + + std::vector mImages; // owning, clients may only borrow! + + RendererProperties mRendererProperties; + const ::vk::Device &mDevice = mRendererProperties.device; + + ::vk::SwapchainKHR mVkSwapchain; + ::vk::SurfaceFormatKHR mWindowColorFormat = {}; + + struct SurfaceProperties + { + bool queried = false; + ::vk::SurfaceCapabilitiesKHR capabilities; + std::vector<::vk::PresentModeKHR> presentmodes; + std::vector<::vk::SurfaceFormatKHR> surfaceFormats; + VkBool32 presentSupported = VK_FALSE; + } mSurfaceProperties; + + void querySurfaceCapabilities(); +public: + + WsiSwapchain( const WsiSwapchainSettings& settings_ ); + + void setRendererProperties( const of::vk::RendererProperties& rendererProperties_ ) override; + + void setup() override; + + virtual ~WsiSwapchain(); + + // Request an image index from the swapchain, so that we might render into it + // the image must be returned to the swapchain when done using queuePresent + // \note this might cause waiting. + ::vk::Result acquireNextImage( ::vk::Semaphore presentCompleteSemaphore, uint32_t &imageIndex ) override; + + + // Present the current image to the queue + // Waits with execution until all waitSemaphores have been signalled + // Pass renderComplete Semaphores here, so present waits for rendering to be complete. + ::vk::Result queuePresent( ::vk::Queue queue, std::mutex & queueMutex, const std::vector<::vk::Semaphore>& waitSemaphores_ ) override; + + // return image by index + const ImageWithView& getImage( size_t i ) const override; + + // return number of swapchain images + const uint32_t getImageCount() const override; + + // return last acquired buffer id + const uint32_t & getCurrentImageIndex() const override; + + const ::vk::Format& getColorFormat() override; + + // Return current swapchain image width in pixels + uint32_t getWidth() override; + + // Return current swapchain image height in pixels + uint32_t getHeight() override; + + // Change width and height in internal settings. + // Caution: this method requires a call to setup() to be applied, and is very costly. + void changeExtent( uint32_t w, uint32_t h ) override; + +}; + + +} // end namespace vk +} // end namespace of diff --git a/libs/openFrameworks/vk/Texture.cpp b/libs/openFrameworks/vk/Texture.cpp new file mode 100644 index 00000000000..3b77f478a11 --- /dev/null +++ b/libs/openFrameworks/vk/Texture.cpp @@ -0,0 +1,88 @@ +#include "vk/Texture.h" +#include "ofVkRenderer.h" + +using namespace std; +using namespace of::vk; + +Texture::Settings::Settings() { + // default sampler create info + samplerInfo + .setMagFilter(::vk::Filter::eLinear) + .setMinFilter(::vk::Filter::eLinear) + .setMipmapMode(::vk::SamplerMipmapMode::eLinear) + .setAddressModeU(::vk::SamplerAddressMode::eClampToBorder) + .setAddressModeV(::vk::SamplerAddressMode::eClampToBorder) + .setAddressModeW(::vk::SamplerAddressMode::eRepeat) + .setMipLodBias(0.f) + .setAnisotropyEnable(VK_FALSE) + .setMaxAnisotropy(0.f) + .setCompareEnable(VK_FALSE) + .setCompareOp(::vk::CompareOp::eLess) + .setMinLod(0.f) + .setMaxLod(1.f) + .setBorderColor(::vk::BorderColor::eFloatTransparentBlack) + .setUnnormalizedCoordinates(VK_FALSE) + ; + + imageViewInfo + .setImage(nullptr) //< there cannot be a default for this + .setViewType(::vk::ImageViewType::e2D) + .setFormat(::vk::Format::eR8G8B8A8Unorm) + .setComponents({ ::vk::ComponentSwizzle::eR, ::vk::ComponentSwizzle::eG, ::vk::ComponentSwizzle::eB, ::vk::ComponentSwizzle::eA }) + .setSubresourceRange({ ::vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 }) + ; +} + +// ---------------------------------------------------------------------- + +void Texture::setup( const Texture::Settings& settings_){ + + mSettings = settings_; + + if (!mSettings.device) { + ofLogError() << "Cannot initialise Texture without device - make sure Texture::Settings::device is set and valid."; + return; + } + + // --------| invariant: device is valid + + // First we reset - so to make sure that no previous vk objects get leaked + // in case a Texture is re-used. + reset(); + + mImageView = mDevice.createImageView(settings_.imageViewInfo); + mSampler = mDevice.createSampler(settings_.samplerInfo); + +} + +// ---------------------------------------------------------------------- + +void Texture::reset() { + if (mDevice) { + + if (mSampler) { + mDevice.destroySampler(mSampler); + mSampler = nullptr; + } + + if (mImageView) { + mDevice.destroyImageView(mImageView); + mImageView = nullptr; + } + + } +} + +// ---------------------------------------------------------------------- + +Texture::~Texture(){ + // Todo: we must find a more elegant way of telling whether + // the sampler or the image view are still in flight or can + // be deleted. + reset(); +} + +// ---------------------------------------------------------------------- + + + diff --git a/libs/openFrameworks/vk/Texture.h b/libs/openFrameworks/vk/Texture.h new file mode 100644 index 00000000000..eb88575f197 --- /dev/null +++ b/libs/openFrameworks/vk/Texture.h @@ -0,0 +1,78 @@ +#pragma once +#include "vulkan/vulkan.hpp" + +/* + +An of::vk::Texture combines a vulkan Image, ImageView and Sampler. +This is mostly for convenience. Note that while the ImageView and +Sampler are owned by of::vk::Texture, the Image itself is not. + +*/ + +namespace of{ +namespace vk{ + +class Texture +{ +public: + struct Settings { + ::vk::Device device = nullptr; + ::vk::SamplerCreateInfo samplerInfo; + ::vk::ImageViewCreateInfo imageViewInfo; + + // Default constructor will initialise Settings with + // sensible values for most createInfo fields. + Settings(); + + inline Settings& setDevice(const ::vk::Device& device_) { + device = device_; + return *this; + } + + inline Settings& setImage(const ::vk::Image& img) { + imageViewInfo.setImage(img); + return *this; + } + }; + +private: + + Settings mSettings; + + ::vk::Device & mDevice = mSettings.device; + ::vk::Sampler mSampler = nullptr; + ::vk::ImageView mImageView = nullptr; + ::vk::ImageLayout mImageLayout = ::vk::ImageLayout::eShaderReadOnlyOptimal; + +public: + + void setup(const Settings& settings_); + void reset(); + + Texture() = default; + Texture(const Texture&) = delete; // no copy constructor please + Texture(const Texture&&) = delete; // no move constructor please + Texture& operator=(const Texture& rhs) & = delete; // no copy assignment constructor please + Texture& operator=(const Texture&& rhs) & = delete; // no move assignment constructor please + ~Texture(); + + const Settings & getSettings() { + return mSettings; + } + + const ::vk::Sampler& getSampler() const { + return mSampler; + } + + const ::vk::ImageView& getImageView() const { + return mImageView; + } + + const ::vk::ImageLayout& getImageLayout() const { + return mImageLayout; + } + +}; + +} /* namespace vk */ +} /* namespace of */ diff --git a/libs/openFrameworks/vk/TransferBatch.cpp b/libs/openFrameworks/vk/TransferBatch.cpp new file mode 100644 index 00000000000..aa436a39f1d --- /dev/null +++ b/libs/openFrameworks/vk/TransferBatch.cpp @@ -0,0 +1,148 @@ +#include "ofLog.h" +#include "vk/TransferBatch.h" + +using namespace of::vk; +/* + +We assume a transfer batch is issued *before* the render batch that might use the buffers for the first time, +from dynamic memory. + +We can ensure this by requesting a transfer batch from a context. + +This means, after the draw batch has been submitted, the draw batch fence being signalled means that the +command buffer used for the batch has completed execution. + +Command buffers execute in sequence - so adding a transfer barrier into the copy command buffer means +that the copy command must have finished executing by the time the draw command buffer is executing. + +Q: How do we tell BufferObjects that their transfer has concluded? +A: They could be added to a per-virtual frame list of buffers which are in transition. +Q: But what happens if a BufferObject is again changed whilst in transition? + +Transfer batch needs to be attached to context. +Context can signal that virtual frame fence has been reached. +Once virtual frame fence was reached, we can dispose of dynamic data. + +*/ + +// ---------------------------------------------------------------------- + +bool TransferBatch::add( std::shared_ptr& buffer ){ + + //// check if buffer can be added + + //if ( !buffer->needsTransfer() ){ + // ofLogVerbose() << "TransferBatch: Buffer does not need transfer."; + // return false; + //} + + //// --------| invariant: buffer needs transfer. + + //// find the first element in the batch that matches the transient and + //// persistent buffer targets of the current buffer - if nothing found, + //// return last element. + //auto it = std::find_if( mBatch.begin(), mBatch.end(), [buffer]( std::shared_ptr lhs ){ + // return buffer->getTransientAllocator()->getBuffer() == lhs->getTransientAllocator()->getBuffer() + // && buffer->getPersistentAllocator()->getBuffer() == lhs->getPersistentAllocator()->getBuffer(); + //} ); + + //mBatch.insert( it, buffer ); + + return true; +} + +// ---------------------------------------------------------------------- + +void TransferBatch::submit(){ + + //if ( mBatch.empty() ){ + // return; + //} + + //auto & device = mRenderContext->getDevice(); + //auto & cmdPool = mRenderContext->getCommandPool(); + + //// First, we need a command buffer where we can record a pipeline barrier command into. + //// This command - the pipeline barrier with an image barrier - will transfer the + //// image resource from its original layout to a layout that the gpu can use for + //// sampling. + //::vk::CommandBuffer cmd = nullptr; + //{ + // ::vk::CommandBufferAllocateInfo cmdBufAllocInfo; + // cmdBufAllocInfo + // .setCommandPool( cmdPool ) + // .setLevel( ::vk::CommandBufferLevel::ePrimary ) + // .setCommandBufferCount( 1 ) + // ; + // cmd = device.allocateCommandBuffers( cmdBufAllocInfo ).front(); + //} + + //std::vector<::vk::BufferCopy> bufferCopies; + //bufferCopies.reserve( mBatch.size() ); + + //::vk::Buffer srcBuf = nullptr; + //::vk::Buffer dstBuf = nullptr; + + //cmd.begin( { ::vk::CommandBufferUsageFlagBits::eOneTimeSubmit } ); + + //for ( auto it = mBatch.cbegin(); it != mBatch.cend(); ++it ){ + // const auto & bufferObject = *it; + + // ::vk::BufferCopy bufferCopy; + // bufferCopy + // .setSize( bufferObject->mRange ) + // .setSrcOffset( bufferObject->mOffset ) + // .setDstOffset( bufferObject->mPersistentOffset ) + // ; + + // // if src or dst are different from last buffer, flush, and enqueue next one. + // if ( bufferObject->getTransientAllocator()->getBuffer() != srcBuf + // || bufferObject->getPersistentAllocator()->getBuffer() != dstBuf ){ + + // if ( bufferCopies.empty() == false ){ + // // submit buffer copies. + // cmd.copyBuffer( srcBuf, dstBuf, bufferCopies ); + // bufferCopies.clear(); + // bufferCopies.reserve( mBatch.size() ); + // } + // srcBuf = bufferObject->getTransientAllocator()->getBuffer(); + // dstBuf = bufferObject->getPersistentAllocator()->getBuffer(); + // } + + // bufferCopies.push_back( bufferCopy ); + + // if ( std::next( it ) == mBatch.cend() && !bufferCopies.empty() ){ + // // submit buffer copies + // cmd.copyBuffer( srcBuf, dstBuf, bufferCopies ); + // } + + //} + + //// CONSIDER: add transfer barrier + //cmd.end(); + + //mRenderContext->submit( std::move( cmd ) ); + + //cmd = nullptr; + + //mInflightBatch.insert( mInflightBatch.end(), mBatch.begin(), mBatch.end()); + //mBatch.clear(); +} + +// ---------------------------------------------------------------------- + +void TransferBatch::signalTransferComplete(){ + + ////!TODO: decrease "inflight" count for each buffer + //// found inside the batch. + + //for ( auto & b : mInflightBatch ){ + // b->setTransferComplete(); + //} + + //mInflightBatch.clear(); + +} + +// ---------------------------------------------------------------------- + diff --git a/libs/openFrameworks/vk/TransferBatch.h b/libs/openFrameworks/vk/TransferBatch.h new file mode 100644 index 00000000000..8265655b158 --- /dev/null +++ b/libs/openFrameworks/vk/TransferBatch.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include "vk/Context.h" + +namespace of{ +namespace vk{ + +/* + +TransferBatch is owned by a rendercontext - there is one transferbatch per virtual frame +in each rendercontext. This is necessary so that buffers may be marked as "transferred" +once the virtual frame has made the round-trip across the virtual frame fence, meaning +that all command buffers within the virtual frame have completed execution. + +Transfer command buffers will be sent to the queue before draw, in the queue submission +triggered by the rendercontext. This submission is bounded by a fence. Once that virtual +frame fence has been waited upon, we can assume safely that all draw commands, and all +transfers have completed execution. + +*/ + +class BufferObject; + +class TransferBatch +{ + friend Context; + + // These methods may only be called by Context + void signalTransferComplete(); + + +private: + + Context * mRenderContext; + + // batch which accumulates all submitted batches whilst the frame is in flight + std::list> mInflightBatch; + // batch which accumulates transfers until submit + std::list> mBatch; + + TransferBatch() = delete; + +public: + + TransferBatch( Context* context_ ) + : mRenderContext(context_) + { + + }; + + ~TransferBatch(){ + // as we don't own anything, there is nothing to destroy. + }; + + bool add( std::shared_ptr& buffer ); + + void submit(); + +}; + + +} // end namespace of::vk +} // end namespace of diff --git a/libs/openFrameworks/vk/ofAppVkNoWindow.cpp b/libs/openFrameworks/vk/ofAppVkNoWindow.cpp new file mode 100644 index 00000000000..2a22132481f --- /dev/null +++ b/libs/openFrameworks/vk/ofAppVkNoWindow.cpp @@ -0,0 +1,192 @@ +#include "ofAppVkNoWindow.h" +#include "ofBaseApp.h" +#include "ofVkRenderer.h" +#include "ImgSwapchain.h" + +using namespace std; + +#if defined TARGET_OSX || defined TARGET_LINUX +#include +#include +#include +#include +#include + +struct termios orig_termios; +struct sigaction act_open; + +void reset_terminal_mode() +{ + tcsetattr(0, TCSANOW, &orig_termios); +} + + +void set_conio_terminal_mode() +{ + struct termios new_termios; + + /* take two copies - one for now, one for later */ + tcgetattr(0, &orig_termios); + memcpy(&new_termios, &orig_termios, sizeof(new_termios)); + + /* register cleanup handler, and set the new terminal mode */ + atexit(reset_terminal_mode); + // setup new_termios for raw keyboard input + cfmakeraw(&new_termios); + // handle "\n" properly + new_termios.c_oflag |= OPOST; + //new_termios.c_oflag |= ONLCR; + // set the new_termios + tcsetattr(0, TCSANOW, &new_termios); +} + +int kbhit() +{ + return 0; + struct timeval tv = { 0L, 0L }; + fd_set fds; + FD_SET(0, &fds); + return select(1, &fds, nullptr, nullptr, &tv); +} + +int getch() +{ + int r; + unsigned char c; + if ((r = read(0, &c, sizeof(c))) < 0) { + return r; + } else { + return c; + } +} + +#endif + +//---------------------------------------------------------- +ofAppVkNoWindow::ofAppVkNoWindow(){ + ofAppPtr = nullptr; + width = 0; + height = 0; +} + +//---------------------------------------------------------- + +void ofAppVkNoWindow::setup(const ofVkWindowSettings & settings){ + width = settings.width; + height = settings.height; + + // create renderer as vkRenderer + auto vkRenderer = make_shared( this, settings.rendererSettings ); + + // Now create a swapchain + { + // Create swapchain based on swapchain settings, + // and swapchainSettings type + of::vk::ImgSwapchainSettings swapchainSettings{}; + swapchainSettings.width = settings.width; + swapchainSettings.height = settings.height; + swapchainSettings.numSwapchainImages = settings.rendererSettings.numSwapchainImages; + swapchainSettings.path = "render/img_"; + swapchainSettings.renderer = vkRenderer; + vkRenderer->setSwapchain( std::make_shared( swapchainSettings ) ); + } + + vkRenderer->setup(); + + // Store renderer ptr + currentRenderer = vkRenderer; +} + +//---------------------------------------------------------- + +void ofAppVkNoWindow::setup( const ofWindowSettings& settings ){ + setup( dynamic_cast(settings) ); +} + +//---------------------------------------------------------- +void ofAppVkNoWindow::update(){ + + /// listen for escape + #ifdef TARGET_WIN32 + if (GetAsyncKeyState(VK_ESCAPE)) + events().notifyKeyPressed(OF_KEY_ESC); + #endif + + #if defined TARGET_OSX || defined TARGET_LINUX + while ( kbhit() ) + { + int key = getch(); + if ( key == 27 ) + { + events().notifyKeyPressed(OF_KEY_ESC); + } + else if ( key == /* ctrl-c */ 3 ) + { + ofLogNotice("ofAppNoWindow") << "Ctrl-C pressed" << endl; + break; + } + else + { + events().notifyKeyPressed(key); + } + } + #endif + + + events().notifyUpdate(); +} + +//---------------------------------------------------------- +void ofAppVkNoWindow::draw(){ + currentRenderer->startRender(); + events().notifyDraw(); + currentRenderer->finishRender(); +} + +//------------------------------------------------------------ +void ofAppVkNoWindow::exitApp(){ + ofLogVerbose("ofAppNoWindow") << "terminating headless (no window) app!"; + +#if defined TARGET_OSX || defined TARGET_LINUX + // this doesn't exist on windows and gives linking errors, so commented out. + reset_terminal_mode(); +#endif + + OF_EXIT_APP(0); +} + +//---------------------------------------------------------- +glm::vec2 ofAppVkNoWindow::getWindowPosition(){ + return {0.f, 0.f}; +} + +//---------------------------------------------------------- +glm::vec2 ofAppVkNoWindow::getWindowSize(){ + return {width, height}; +} + +//---------------------------------------------------------- +glm::vec2 ofAppVkNoWindow::getScreenSize(){ + return {width, height}; +} + + +//---------------------------------------------------------- +int ofAppVkNoWindow::getWidth(){ + return width; +} + +//---------------------------------------------------------- +int ofAppVkNoWindow::getHeight(){ + return height; +} + + +//---------------------------------------------------------- +ofCoreEvents & ofAppVkNoWindow::events(){ + return coreEvents; +} + +shared_ptr & ofAppVkNoWindow::renderer(){ + return currentRenderer; +} diff --git a/libs/openFrameworks/vk/ofAppVkNoWindow.h b/libs/openFrameworks/vk/ofAppVkNoWindow.h new file mode 100644 index 00000000000..5deb628dbcb --- /dev/null +++ b/libs/openFrameworks/vk/ofAppVkNoWindow.h @@ -0,0 +1,47 @@ +#pragma once + +#include "ofConstants.h" +#include "ofAppBaseWindow.h" + + +class ofBaseApp; +class ofVkRenderer; + +class ofAppVkNoWindow : public ofAppBaseWindow { + +public: + ofAppVkNoWindow(); + ~ofAppVkNoWindow(){} + + static bool doesLoop(){ return false; } + static bool allowsMultiWindow(){ return false; } + static void loop(){}; + static bool needsPolling(){ return false; } + static void pollEvents(){}; + + void run(ofBaseApp * appPtr); + + static void exitApp(); + void setup(const ofWindowSettings & settings); + void setup( const ofVkWindowSettings& settings ); + + void update(); + void draw(); + + glm::vec2 getWindowPosition(); + glm::vec2 getWindowSize(); + glm::vec2 getScreenSize(); + + int getWidth(); + int getHeight(); + + ofCoreEvents & events(); + std::shared_ptr & renderer(); + +private: + int width, height; + + ofBaseApp * ofAppPtr; + ofCoreEvents coreEvents; + std::shared_ptr currentRenderer; +}; diff --git a/libs/openFrameworks/vk/ofVkRenderer.cpp b/libs/openFrameworks/vk/ofVkRenderer.cpp new file mode 100644 index 00000000000..90b960d5e94 --- /dev/null +++ b/libs/openFrameworks/vk/ofVkRenderer.cpp @@ -0,0 +1,665 @@ +#include "ofVkRenderer.h" +#include "ofMesh.h" +#include "ofPath.h" +#include "ofBitmapFont.h" +#include "ofImage.h" +#include "of3dPrimitives.h" +#include "ofLight.h" +#include "ofMaterial.h" +#include "ofCamera.h" +#include "ofTrueTypeFont.h" +#include "ofNode.h" +#include "GLFW/glfw3.h" + +#include + +using namespace std; + +const string ofVkRenderer::TYPE = "Vulkan"; + +using InstanceP = shared_ptr; + +// ---------------------------------------------------------------------- + +// fetched function pointers for debug layer callback creation / destruction +// functions. As these are not directly exposed within the sdk, we have to +// first query the sdk for the function pointers to these. +PFN_vkCreateDebugReportCallbackEXT fVkCreateDebugReportCallbackEXT = nullptr; +PFN_vkDestroyDebugReportCallbackEXT fVkDestroyDebugReportCallbackEXT = nullptr; + +// ---------------------------------------------------------------------- + +ofVkRenderer::ofVkRenderer(const ofAppBaseWindow * _window, of::vk::RendererSettings settings ) + : m3dGraphics(this) + , mSettings(settings) +{ + bBackgroundAuto = true; + wrongUseLoggedOnce = false; + + currentMaterial = nullptr; + + window = _window; + + mPath.setMode(ofPath::POLYLINES); + mPath.setUseShapeColor(false); + + if ( mSettings.useDebugLayers ){ + requestDebugLayers(); + } + +#ifdef TARGET_LINUX + mInstanceExtensions.push_back( "VK_KHR_xcb_surface" ); +#endif +#ifdef TARGET_WIN32 + mInstanceExtensions.push_back( "VK_KHR_win32_surface" ); +#endif + mInstanceExtensions.push_back( "VK_KHR_surface" ); + mDeviceExtensions.push_back( "VK_KHR_swapchain" ); + + createInstance(); + + // important to call createDebugLayers() after createInstance, + // since otherwise the debug layer create function pointers will not be + // correctly resolved, since the main library would not yet have been loaded. + if ( mSettings.useDebugLayers ){ + createDebugLayers(); + } + // createDevice also initialises the device queue, mQueue + createDevice(); + + mPipelineCache = of::vk::createPipelineCache( mDevice, "pipelineCache.bin" ); + + // We add an event listener for after app setup, so that we may submit any + // transfer command buffers which may have been issued during app setup. + ofAddListener( ofEvents().setup, this, &ofVkRenderer::postSetup, ofEventOrder::OF_EVENT_ORDER_AFTER_APP ); + + // setup transfer context - this context is used to stage + setupStagingContext(); + + mStagingContext->begin(); + // Up next: create window surface (this happens within glfw) +} +// ---------------------------------------------------------------------- + +void ofVkRenderer::postSetup( ofEventArgs & args ){ + mStagingContext->end(); +} + +// ---------------------------------------------------------------------- + + +const vk::Instance& ofVkRenderer::getInstance() { + return mInstance; +} + +// ---------------------------------------------------------------------- + +ofVkRenderer::~ofVkRenderer() +{ + // Tell GPU to finish whatever it is doing + // and to catch up with the CPU waiting right here. + // + // This is a sync method so harsh, it should + // only ever be used for teardown. + // + // Which is what this method is doing. + mDevice.waitIdle(); + + mDefaultContext.reset(); + mStagingContext.reset(); + + mDepthStencil.reset(); + + mSwapchain.reset(); + mPipelineCache.reset(); + + mDefaultRenderPass.reset(); + + // Reset command pool and all associated command buffers. + + destroyDevice(); + + // Todo: destroy vkSurface + // The surface was created in glfwWindow, so it should be destroyed there, too. + // it must be destroyed before the instance is destroyed, meaning at this point. + + destroyDebugLayers(); + destroyInstance(); +} + +// ---------------------------------------------------------------------- + +void ofVkRenderer::createInstance(){ + ofLog() << "Creating instance."; + + std::string appName = "openFrameworks" + ofGetVersionInfo(); + + vk::ApplicationInfo applicationInfo; + applicationInfo + .setApiVersion( mSettings.vkVersion ) + .setApplicationVersion( VK_MAKE_VERSION( 0, 1, 0 ) ) + .setPApplicationName( appName.c_str() ) + .setPEngineName( "openFrameworks Vulkan Renderer" ) + .setEngineVersion( VK_MAKE_VERSION( 0, 0, 0 ) ) + ; + + vk::InstanceCreateInfo instanceCreateInfo; + instanceCreateInfo + .setPApplicationInfo ( &applicationInfo ) + .setEnabledLayerCount ( mInstanceLayers.size() ) + .setPpEnabledLayerNames ( mInstanceLayers.data() ) + .setEnabledExtensionCount ( mInstanceExtensions.size() ) + .setPpEnabledExtensionNames ( mInstanceExtensions.data() ) + .setPNext ( &mDebugCallbackCreateInfo ) /* <-- enables debugging the instance creation. */ + ; + + mInstance = vk::createInstance( instanceCreateInfo ); + ofLog() << "Successfully created instance."; +} + +// ---------------------------------------------------------------------- + +void ofVkRenderer::destroyInstance() +{ + mInstance.destroy(); +} + +// ---------------------------------------------------------------------- + +uint32_t findQueueFamilyIndex( const std::vector& props, const ::vk::QueueFlags & flags ){ + + // Find out the queue family index for a queue best matching the given flags. + // We use this to find out the index of the Graphics Queue for example. + + for ( uint32_t familyIndex = 0; familyIndex != props.size(); familyIndex++ ){ + if ( props[familyIndex].queueFlags == flags ){ + // First perfect match + return familyIndex; + } + } + + for ( uint32_t familyIndex = 0; familyIndex != props.size(); familyIndex++ ){ + if ( props[familyIndex].queueFlags & flags ){ + // First multi-function queue match + return familyIndex; + } + } + + // ---------| invariant: no queue found + + ofLogWarning() << "VkRenderer: Could not find queue family index matching: " << ::vk::to_string( flags ); + + return ~( uint32_t( 0 ) ); +} + + +// ---------------------------------------------------------------------- +/// \brief Find best match for a vector or queues defined by queueFamiliyProperties flags +/// \note For each entry in the result vector the tuple values represent: +/// 0.. best matching queue family +/// 1.. index within queue family +/// 2.. index of queue from props vector (used to keep queue indices +// consistent between requested queues and queues you will render to) +std::vector> findBestMatchForRequestedQueues( const std::vector& props, const std::vector<::vk::QueueFlags>& reqProps ){ + std::vector> result; + + std::vector usedQueues( props.size(), ~( uint32_t(0) ) ); // last used queue, per queue family (initialised at -1) + + size_t reqIdx = 0; // original index for requested queue + for ( const auto & flags : reqProps ){ + + // best match is a queue which does exclusively what we want + bool foundMatch = false; + uint32_t foundFamily = 0; + uint32_t foundIndex = 0; + + for ( uint32_t familyIndex = 0; familyIndex != props.size(); familyIndex++ ){ + + // 1. Is there a family that matches our requirements exactly? + // 2. Is a queue from this family still available? + + if ( props[familyIndex].queueFlags == flags ){ + // perfect match + if ( usedQueues[familyIndex] + 1 < props[familyIndex].queueCount ){ + foundMatch = true; + foundFamily = familyIndex; + foundIndex = usedQueues[familyIndex] + 1; + ofLog() << "Found dedicated queue matching: " << ::vk::to_string( flags ); + } else{ + ofLogWarning() << "No more dedicated queues available matching: " << ::vk::to_string( flags ); + } + break; + } + } + + if ( foundMatch == false ){ + + // If we haven't found a match, we need to find a versatile queue which might + // be able to fulfill our requirements. + + for ( uint32_t familyIndex = 0; familyIndex != props.size(); familyIndex++ ){ + + // 1. Is there a family that has the ability to fulfill our requirements? + // 2. Is a queue from this family still available? + + if ( props[familyIndex].queueFlags & flags ){ + // versatile queue match + if ( usedQueues[familyIndex] + 1 < props[familyIndex].queueCount ){ + foundMatch = true; + foundFamily = familyIndex; + foundIndex = usedQueues[familyIndex] + 1; + ofLog() << "Found versatile queue matching: " << ::vk::to_string( flags ); + } + break; + } + } + } + + if ( foundMatch ){ + result.emplace_back( foundFamily, foundIndex, reqIdx ); + usedQueues[foundFamily] = foundIndex; // mark this queue as used + } else{ + ofLogError() << "No available queue matching requirement: " << ::vk::to_string( flags ); + ofExit( -1 ); + } + + ++reqIdx; + } + + return result; +} + +// ---------------------------------------------------------------------- + +void ofVkRenderer::createDevice() +{ + // enumerate physical devices list to find + // first available device + auto deviceList = mInstance.enumeratePhysicalDevices(); + + // CONSIDER: find the best appropriate GPU + // Select a physical device (GPU) from the above queried list of options. + // For now, we assume the first one to be the best one. + mPhysicalDevice = deviceList.front(); + + // query the gpu for more info about itself + mPhysicalDeviceProperties = mPhysicalDevice.getProperties(); + + ofLog() << "GPU Type: " << mPhysicalDeviceProperties.deviceName; + + { + of::vk::RendererSettings tmpSettings; + tmpSettings.vkVersion = mPhysicalDeviceProperties.apiVersion; + ofLog() << "GPU API Version: " << tmpSettings.getVkVersionMajor() << "." + << tmpSettings.getVersionMinor() << "." << tmpSettings.getVersionPatch(); + + uint32_t driverVersion = mPhysicalDeviceProperties.driverVersion; + ofLog() << "GPU Driver Version: " << std::hex << driverVersion; + } + + // let's find out the devices' memory properties + mPhysicalDeviceMemoryProperties = mPhysicalDevice.getMemoryProperties(); + + // query debug layers available for instance + { + std::ostringstream console; + + vector instanceLayerPropertyList = vk::enumerateInstanceLayerProperties(); + + console << "Available Instance Layers:" << std::endl << std::endl; + for (auto &l : instanceLayerPropertyList ) { + console << std::right << std::setw( 40 ) << l.layerName << " : " << l.description << std::endl; + } + ofLog() << console.str(); + } + + // query debug layers available for physical device + { + std::ostringstream console; + + vector deviceLayerPropertylist = mPhysicalDevice.enumerateDeviceLayerProperties(); + + console << "Available Device Layers:" << std::endl << std::endl; + for (auto &l : deviceLayerPropertylist ) { + console << std::right << std::setw( 40 ) << l.layerName << " : " << l.description << std::endl; + } + ofLog() << console.str(); + } + + // Check which features must be switched on for default openFrameworks operations. + // For now, we just make sure we can draw with lines. + // + // We should put this into the renderer setttings. + vk::PhysicalDeviceFeatures deviceFeatures = mPhysicalDevice.getFeatures(); + deviceFeatures + .setFillModeNonSolid( VK_TRUE ) // allow wireframe drawing + ; + + + const auto & queueFamilyProperties = mPhysicalDevice.getQueueFamilyProperties(); + + // Find out family index for graphics queue + mRendererProperties.graphicsFamilyIndex = findQueueFamilyIndex( queueFamilyProperties, ::vk::QueueFlagBits::eGraphics ); + mRendererProperties.computeFamilyIndex = findQueueFamilyIndex( queueFamilyProperties, ::vk::QueueFlagBits::eCompute ); + mRendererProperties.transferFamilyIndex = findQueueFamilyIndex( queueFamilyProperties, ::vk::QueueFlagBits::eTransfer ); + mRendererProperties.sparseBindingFamilyIndex = findQueueFamilyIndex( queueFamilyProperties, ::vk::QueueFlagBits::eSparseBinding ); + + // See findBestMatchForRequestedQueues for how this tuple is laid out. + auto queriedQueueFamilyAndIndex = findBestMatchForRequestedQueues( queueFamilyProperties, mSettings.requestedQueues ); + + // Consolidate queues by queue family type - this will also sort by queue family type. + { + std::map queueCountPerFamily; // queueFamily -> count + + for (const auto & q : queriedQueueFamilyAndIndex){ + // Attempt to insert family to map + auto insertResult = queueCountPerFamily.insert({std::get<0>(q),1}); + if (false == insertResult.second){ + // Increment count if family entry already existed in map. + insertResult.first->second ++; + } + } + + // Create queues based on queriedQueueFamilyAndIndex + std::vector<::vk::DeviceQueueCreateInfo> createInfos; + createInfos.reserve( queriedQueueFamilyAndIndex.size() ); + + // We must store this in a map so that the pointer stays + // alive until we call the api. + std::map> prioritiesPerFamily; + + for ( auto & q : queueCountPerFamily ){ + vk::DeviceQueueCreateInfo queueCreateInfo; + const auto & queueFamily = q.first; + const auto & queueCount = q.second; + prioritiesPerFamily[queueFamily].resize(queueCount, 1.f); // all queues have the same priority, 1. + queueCreateInfo + .setQueueFamilyIndex( queueFamily ) + .setQueueCount( queueCount ) + .setPQueuePriorities( prioritiesPerFamily[queueFamily].data() ) + ; + createInfos.emplace_back( std::move( queueCreateInfo ) ); + } + + vk::DeviceCreateInfo deviceCreateInfo; + deviceCreateInfo + .setQueueCreateInfoCount ( createInfos.size() ) + .setPQueueCreateInfos ( createInfos.data() ) + .setEnabledLayerCount ( mDeviceLayers.size() ) + .setPpEnabledLayerNames ( mDeviceLayers.data() ) + .setEnabledExtensionCount ( mDeviceExtensions.size() ) + .setPpEnabledExtensionNames ( mDeviceExtensions.data() ) + .setPEnabledFeatures ( &deviceFeatures ) + ; + + // Create device + mDevice = mPhysicalDevice.createDevice( deviceCreateInfo ); + + } + + ofLogNotice() << "Successfully created Vulkan device"; + + + // Store queue flags, and queue family index per queue into renderer properties, + // so that queue capabilities and family index may be queried thereafter. + mRendererProperties.queueFlags = mSettings.requestedQueues; + mRendererProperties.queueFamilyIndices.resize( mSettings.requestedQueues.size() ); + + mQueues.resize(queriedQueueFamilyAndIndex.size()); + + // Fetch queue handle into mQueue, matching indices with the original queue request vector + for ( auto & q : queriedQueueFamilyAndIndex ){ + const auto & queueFamilyIndex = std::get<0>(q); + const auto & queueIndex = std::get<1>(q); + const auto & requestedQueueIndex = std::get<2>(q); + mQueues[requestedQueueIndex] = mDevice.getQueue( queueFamilyIndex, queueIndex ); + mRendererProperties.queueFamilyIndices[requestedQueueIndex] = queueFamilyIndex; + } + + // Create mutexes to protect each queue + mQueueMutex = std::vector( mQueues.size() ); + + // Query possible depth formats, find the + // first format that supports attachment as a depth stencil + // + // Since all depth formats may be optional, we need to find a suitable depth format to use + // Start with the highest precision packed format + std::vector depthFormats = { + vk::Format::eD32SfloatS8Uint, + vk::Format::eD32Sfloat, + vk::Format::eD24UnormS8Uint, + vk::Format::eD16Unorm, + vk::Format::eD16UnormS8Uint + }; + + for ( auto& format : depthFormats ){ + vk::FormatProperties formatProps = mPhysicalDevice.getFormatProperties( format ); + // Format must support depth stencil attachment for optimal tiling + if ( formatProps.optimalTilingFeatures & vk::FormatFeatureFlagBits::eDepthStencilAttachment){ + mDepthFormat = format; + break; + } + } + +} + +// ---------------------------------------------------------------------- + +void ofVkRenderer::destroyDevice() +{ + mDevice.destroy(); +} + +// ---------------------------------------------------------------------- + +VKAPI_ATTR VkBool32 VKAPI_CALL +VulkanDebugCallback( + VkDebugReportFlagsEXT flags, // what kind of error are we handling + VkDebugReportObjectTypeEXT objType, // type of object that caused the error + uint64_t srcObj, // pointer to the object that caused the error + size_t location, // ? could be source code line ? + int32_t msgCode, // ? how important this callback is ? + const char* layer_prefix, // which layer called this callback + const char* msg, // user readable string + void * userData +) { + +#ifdef WIN32 + static HANDLE hConsole = GetStdHandle( STD_OUTPUT_HANDLE ); + if ( flags & VK_DEBUG_REPORT_ERROR_BIT_EXT ){ + SetConsoleTextAttribute( hConsole, 12 + 0 * 16 ); + } +#endif // WIN32 + + bool shouldBailout = false; + std::string logLevel = ""; + + if (flags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT) { + logLevel = "INFO"; + } else if (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT) { + logLevel = "WARN"; + } else if (flags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT) { + logLevel = "PERF"; + } else if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) { + logLevel = "ERROR"; + shouldBailout |= true; + } else if (flags & VK_DEBUG_REPORT_DEBUG_BIT_EXT) { + logLevel = "DEBUG"; + } + + std::ostringstream os; + os << std::right << std::setw( 8 ) << logLevel << "{" << std::setw( 10 ) << layer_prefix << "}: " << msg << std::endl; + + ofLogNotice() << (os.str().substr(0,os.str().length()-1)); +#ifdef WIN32 + SetConsoleTextAttribute( hConsole, 7 + 0 * 16 ); +#endif + // if error returns true, this layer will try to bail out and not forward the command + return shouldBailout; +} + +// ---------------------------------------------------------------------- + +void ofVkRenderer::requestDebugLayers() { + + mInstanceLayers.push_back( "VK_LAYER_LUNARG_standard_validation" ); + mInstanceLayers.push_back("VK_LAYER_LUNARG_object_tracker"); + mInstanceExtensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); + mDeviceLayers.push_back("VK_LAYER_LUNARG_standard_validation"); + +} +// ---------------------------------------------------------------------- + +void ofVkRenderer::createDebugLayers() +{ + mDebugCallbackCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT; + mDebugCallbackCreateInfo.pfnCallback = &VulkanDebugCallback; + mDebugCallbackCreateInfo.flags = + //VK_DEBUG_REPORT_INFORMATION_BIT_EXT | + VK_DEBUG_REPORT_WARNING_BIT_EXT + | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT + | VK_DEBUG_REPORT_ERROR_BIT_EXT + | VK_DEBUG_REPORT_DEBUG_BIT_EXT + | 0; // this should enable all flags. + + // first get (find) function pointers from sdk for callback [create / destroy] function addresses + fVkCreateDebugReportCallbackEXT = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(mInstance, "vkCreateDebugReportCallbackEXT"); + fVkDestroyDebugReportCallbackEXT = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(mInstance, "vkDestroyDebugReportCallbackEXT"); + + // we can't check against nullptr here, since 0x0 is not the same as nullptr and + // we would falsely get a positive feedback even if the sdk returns 0x0 as the address + // for the function pointers. + if (VK_NULL_HANDLE == fVkCreateDebugReportCallbackEXT || VK_NULL_HANDLE == fVkDestroyDebugReportCallbackEXT) { + ofLogError() << "error fetching pointers for debug layer callbacks"; + ofExit(-1); + return; + } + + // this method is not available by default. + { + // note that we execute the function pointers we searched for earlier, + // since "vkCreateDebugReportCallbackEXT" is not directly exposed by vulkan-1.lib + // fVkCreateDebugReportCallbackEXT is the function we want to call. + fVkCreateDebugReportCallbackEXT(mInstance, &mDebugCallbackCreateInfo , VK_NULL_HANDLE, &mDebugReportCallback); + } +} + +// ---------------------------------------------------------------------- + +void ofVkRenderer::destroyDebugLayers() +{ + if ( mDebugReportCallback != VK_NULL_HANDLE ){ + fVkDestroyDebugReportCallbackEXT( mInstance, mDebugReportCallback, VK_NULL_HANDLE ); + // let's set our own callback address to 0 just to be on the safe side. + mDebugReportCallback = VK_NULL_HANDLE; + } +} +// ---------------------------------------------------------------------- + +ofRectangle ofVkRenderer::getCurrentViewport() const +{ + return mViewport; +} + +// ---------------------------------------------------------------------- + +ofRectangle ofVkRenderer::getNativeViewport() const +{ + return mViewport; +} + +// ---------------------------------------------------------------------- + +int ofVkRenderer::getViewportWidth() const +{ + return mViewport.width; +} + +// ---------------------------------------------------------------------- + +int ofVkRenderer::getViewportHeight() const +{ + return mViewport.height; +} + +// ---------------------------------------------------------------------- + +bool ofVkRenderer::isVFlipped() const +{ + return false; +} + +// ---------------------------------------------------------------------- + +ofHandednessType ofVkRenderer::getCoordHandedness() const +{ + return ofHandednessType(); +} + +inline glm::mat4x4 ofVkRenderer::getCurrentMatrix( ofMatrixMode matrixMode_ ) const{ + return glm::mat4x4(); +} + +inline glm::mat4x4 ofVkRenderer::getCurrentOrientationMatrix() const{ + return glm::mat4x4(); +} + +// ---------------------------------------------------------------------- + + +ofRectMode ofVkRenderer::getRectMode() +{ + return ofRectMode(); +} + +// ---------------------------------------------------------------------- + +ofFillFlag ofVkRenderer::getFillMode() +{ + return ofFillFlag(); +} + +// ---------------------------------------------------------------------- + +inline ofColor ofVkRenderer::getBackgroundColor(){ + return ofColor(); +} + +bool ofVkRenderer::getBackgroundAuto(){ + return bBackgroundAuto; +} + +// ---------------------------------------------------------------------- + +ofPath & ofVkRenderer::getPath(){ + return mPath; +} + +inline ofStyle ofVkRenderer::getStyle() const{ + return ofStyle(); +} + +// ---------------------------------------------------------------------- + +const of3dGraphics & ofVkRenderer::get3dGraphics() const +{ + return m3dGraphics; +} + +// ---------------------------------------------------------------------- + +of3dGraphics & ofVkRenderer::get3dGraphics() +{ + return m3dGraphics; +} + +// ---------------------------------------------------------------------- + +glm::mat4x4 ofVkRenderer::getCurrentViewMatrix() const{ + return glm::mat4x4(); +} + +// ---------------------------------------------------------------------- +glm::mat4x4 ofVkRenderer::getCurrentNormalMatrix() const{ + return glm::mat4x4(); +} diff --git a/libs/openFrameworks/vk/ofVkRenderer.h b/libs/openFrameworks/vk/ofVkRenderer.h new file mode 100644 index 00000000000..ff93df9672e --- /dev/null +++ b/libs/openFrameworks/vk/ofVkRenderer.h @@ -0,0 +1,397 @@ +#pragma once + +#include + +#include "vk/Swapchain.h" +#include "vk/HelperTypes.h" +#include "vk/BufferAllocator.h" +#include "vk/ImageAllocator.h" +#include "vk/DrawCommand.h" +#include "vk/ComputeCommand.h" +#include "vk/RenderBatch.h" +#include "vk/Texture.h" + +#include "ofBaseTypes.h" +#include "ofPolyline.h" +#include "ofMatrix4x4.h" +#include "ofMatrixStack.h" +#include "of3dGraphics.h" +#include "ofBitmapFont.h" +#include "ofPath.h" +#include "ofMesh.h" +#include + +#define RENDERER_FUN_NOT_IMPLEMENTED { \ + ofLogVerbose() << __FUNCTION__ << ": not implemented in VkRenderer.";\ +} \ + + +class ofShapeTessellation; + + +namespace of{ +namespace vk{ + class Context; + class Shader; +} // end namespace of::vk +}; // end namespace of + + +class ofVkRenderer : public ofBaseRenderer +{ + + bool bBackgroundAuto; + bool wrongUseLoggedOnce; + + const ofBaseMaterial * currentMaterial; + + ofStyle currentStyle; + std::deque styleHistory; + of3dGraphics m3dGraphics; + ofBitmapFont bitmapFont; + ofPath mPath; + const ofAppBaseWindow * window; + +public: + static const std::string TYPE; + + const of::vk::RendererSettings mSettings; + + ofVkRenderer( const ofAppBaseWindow * window, of::vk::RendererSettings settings ); + + void setup(); + virtual ~ofVkRenderer() override; + + virtual const std::string & getType() override{ + return TYPE; + }; + + virtual void startRender() override; + virtual void finishRender() override; + + const uint32_t getSwapChainSize(); + + virtual void draw( const ofPolyline & poly )const RENDERER_FUN_NOT_IMPLEMENTED; + virtual void draw( const ofPath & shape ) const RENDERER_FUN_NOT_IMPLEMENTED; + virtual void draw( const ofMesh & vertexData, ofPolyRenderMode renderType, bool useColors, bool useTextures, bool useNormals ) const RENDERER_FUN_NOT_IMPLEMENTED; + virtual void draw( const of3dPrimitive& model, ofPolyRenderMode renderType ) const RENDERER_FUN_NOT_IMPLEMENTED; + virtual void draw( const ofNode& model ) const RENDERER_FUN_NOT_IMPLEMENTED; + virtual void draw( const ofImage & image, float x, float y, float z, float w, float h, float sx, float sy, float sw, float sh ) const RENDERER_FUN_NOT_IMPLEMENTED; + virtual void draw( const ofFloatImage & image, float x, float y, float z, float w, float h, float sx, float sy, float sw, float sh ) const RENDERER_FUN_NOT_IMPLEMENTED; + virtual void draw( const ofShortImage & image, float x, float y, float z, float w, float h, float sx, float sy, float sw, float sh ) const RENDERER_FUN_NOT_IMPLEMENTED; + virtual void draw( const ofBaseVideoDraws & video, float x, float y, float w, float h ) const RENDERER_FUN_NOT_IMPLEMENTED; + + virtual void pushView() RENDERER_FUN_NOT_IMPLEMENTED; + virtual void popView() RENDERER_FUN_NOT_IMPLEMENTED; + + virtual void viewport( ofRectangle viewport ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void viewport( float x = 0, float y = 0, float width = -1, float height = -1, bool vflip = true ) RENDERER_FUN_NOT_IMPLEMENTED; + + virtual void setupScreenPerspective( float width = -1, float height = -1, float fov = 60, float nearDist = 0, float farDist = 0 ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void setupScreenOrtho( float width = -1, float height = -1, float nearDist = -1, float farDist = 1 ) RENDERER_FUN_NOT_IMPLEMENTED; + + virtual void setOrientation( ofOrientation orientation, bool vFlip ) RENDERER_FUN_NOT_IMPLEMENTED; + + virtual ofRectangle getCurrentViewport() const override; + virtual ofRectangle getNativeViewport() const override; + virtual int getViewportWidth() const override; + virtual int getViewportHeight() const override; + + virtual bool isVFlipped() const override; + virtual void setCoordHandedness( ofHandednessType handedness ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual ofHandednessType getCoordHandedness() const override; + + virtual void pushMatrix() override RENDERER_FUN_NOT_IMPLEMENTED; + virtual void popMatrix() override RENDERER_FUN_NOT_IMPLEMENTED; + + virtual glm::mat4x4 getCurrentMatrix( ofMatrixMode matrixMode_ ) const; + virtual glm::mat4x4 getCurrentOrientationMatrix() const; + + virtual void translate( float x, float y, float z = 0 ) RENDERER_FUN_NOT_IMPLEMENTED; + + virtual void translate( const glm::vec3& p ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void scale( float xAmnt, float yAmnt, float zAmnt = 1 ) RENDERER_FUN_NOT_IMPLEMENTED; + + virtual void rotateRad( float degrees, float axisX, float axisY, float axisZ ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void rotateXRad( float degrees ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void rotateYRad( float degrees ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void rotateZRad( float degrees ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void rotateRad( float degrees ) RENDERER_FUN_NOT_IMPLEMENTED; + + virtual void matrixMode( ofMatrixMode mode ) RENDERER_FUN_NOT_IMPLEMENTED; + + + virtual void loadMatrix( const glm::mat4x4 & m ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void loadMatrix( const float *m ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void loadIdentityMatrix( void ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void loadViewMatrix( const glm::mat4x4 & m )RENDERER_FUN_NOT_IMPLEMENTED; + virtual void multViewMatrix( const glm::mat4x4 & m )RENDERER_FUN_NOT_IMPLEMENTED; + virtual void multMatrix( const glm::mat4x4 & m )RENDERER_FUN_NOT_IMPLEMENTED; + virtual void multMatrix( const float *m )RENDERER_FUN_NOT_IMPLEMENTED; + + virtual glm::mat4x4 getCurrentViewMatrix() const override; + virtual glm::mat4x4 getCurrentNormalMatrix() const override; + + virtual void bind( const ofCamera & camera, const ofRectangle & viewport ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void unbind( const ofCamera & camera ) RENDERER_FUN_NOT_IMPLEMENTED; + + virtual void setupGraphicDefaults()RENDERER_FUN_NOT_IMPLEMENTED; + + virtual void setupScreen()RENDERER_FUN_NOT_IMPLEMENTED; + + void resizeScreen( int w, int h ); + + virtual void setRectMode( ofRectMode mode )RENDERER_FUN_NOT_IMPLEMENTED; + + virtual ofRectMode getRectMode() override; + + virtual void setFillMode( ofFillFlag fill ) RENDERER_FUN_NOT_IMPLEMENTED; + + virtual ofFillFlag getFillMode() override; + + virtual void setLineWidth( float lineWidth ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void setDepthTest( bool depthTest ) RENDERER_FUN_NOT_IMPLEMENTED; + + virtual void setBlendMode( ofBlendMode blendMode )RENDERER_FUN_NOT_IMPLEMENTED; + virtual void setLineSmoothing( bool smooth ) RENDERER_FUN_NOT_IMPLEMENTED; + + virtual void setCircleResolution( int res ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void enableAntiAliasing() RENDERER_FUN_NOT_IMPLEMENTED; + virtual void disableAntiAliasing() RENDERER_FUN_NOT_IMPLEMENTED; + + virtual void setColor( int r, int g, int b ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void setColor( int r, int g, int b, int a ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void setColor( const ofColor & color ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void setColor( const ofColor & color, int _a ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void setColor( int gray ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void setHexColor( int hexColor ) RENDERER_FUN_NOT_IMPLEMENTED; + + virtual void setBitmapTextMode( ofDrawBitmapMode mode ) RENDERER_FUN_NOT_IMPLEMENTED; + + virtual ofColor getBackgroundColor(); + virtual void setBackgroundColor( const ofColor & c ) RENDERER_FUN_NOT_IMPLEMENTED; + + virtual void background( const ofColor & c ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void background( float brightness ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void background( int hexColor, float _a = 255.0f ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void background( int r, int g, int b, int a = 255 ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void setBackgroundAuto( bool bManual ) RENDERER_FUN_NOT_IMPLEMENTED; + + virtual bool getBackgroundAuto() override; + + virtual void clear() RENDERER_FUN_NOT_IMPLEMENTED; + virtual void clear( float r, float g, float b, float a = 0 ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void clear( float brightness, float a = 0 ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void clearAlpha() RENDERER_FUN_NOT_IMPLEMENTED; + + virtual void drawLine( float x1, float y1, float z1, float x2, float y2, float z2 ) const RENDERER_FUN_NOT_IMPLEMENTED; + virtual void drawRectangle( float x, float y, float z, float w, float h ) const RENDERER_FUN_NOT_IMPLEMENTED; + virtual void drawTriangle( float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3 ) const RENDERER_FUN_NOT_IMPLEMENTED; + virtual void drawCircle( float x, float y, float z, float radius ) const RENDERER_FUN_NOT_IMPLEMENTED; + virtual void drawEllipse( float x, float y, float z, float width, float height ) const RENDERER_FUN_NOT_IMPLEMENTED; + virtual void drawString( std::string text, float x, float y, float z ) const RENDERER_FUN_NOT_IMPLEMENTED; + virtual void drawString( const ofTrueTypeFont & font, std::string text, float x, float y ) const RENDERER_FUN_NOT_IMPLEMENTED; + + virtual ofPath & getPath() override; + virtual ofStyle getStyle() const; + virtual void setStyle( const ofStyle & style ) RENDERER_FUN_NOT_IMPLEMENTED; + virtual void pushStyle() RENDERER_FUN_NOT_IMPLEMENTED; + virtual void popStyle() RENDERER_FUN_NOT_IMPLEMENTED; + + virtual void setCurveResolution( int resolution ) RENDERER_FUN_NOT_IMPLEMENTED; + + virtual void setPolyMode( ofPolyWindingMode mode ) RENDERER_FUN_NOT_IMPLEMENTED; + + virtual const of3dGraphics & get3dGraphics() const override; + virtual of3dGraphics & get3dGraphics() override; + +private: + + + void createInstance(); + void destroyInstance(); + + void createDevice(); + void destroyDevice(); + + //void destroySurface(); + + VkDebugReportCallbackEXT mDebugReportCallback = nullptr; + VkDebugReportCallbackCreateInfoEXT mDebugCallbackCreateInfo = {}; + + void requestDebugLayers(); + void createDebugLayers(); + void destroyDebugLayers(); + + of::vk::RendererProperties mRendererProperties; + + ::vk::Instance &mInstance = mRendererProperties.instance; + ::vk::Device &mDevice = mRendererProperties.device; + ::vk::PhysicalDevice &mPhysicalDevice = mRendererProperties.physicalDevice; + ::vk::PhysicalDeviceProperties &mPhysicalDeviceProperties = mRendererProperties.physicalDeviceProperties; + ::vk::PhysicalDeviceMemoryProperties &mPhysicalDeviceMemoryProperties = mRendererProperties.physicalDeviceMemoryProperties; + uint32_t &mVkGraphicsQueueFamilyIndex = mRendererProperties.graphicsFamilyIndex; + + std::vector mInstanceLayers; // debug layer list for instance + std::vector mInstanceExtensions; // debug layer list for device + std::vector mDeviceLayers; // debug layer list for device + std::vector mDeviceExtensions; // debug layer list for device + + std::shared_ptr<::vk::PipelineCache> mPipelineCache; + +public: + + // return handle to renderer's vkDevice + // CONSIDER: error checking for when device has not been aqcuired yet. + const ::vk::Device& getVkDevice() const; + + const ::vk::PhysicalDeviceProperties& getVkPhysicalDeviceProperties() const; + + const ::vk::PhysicalDeviceMemoryProperties& getVkPhysicalDeviceMemoryProperties() const; + + // Return requested number of virtual frames (n.b. that's not swapchain frames) for this renderer + // virtual frames are frames that are produced and submitted to the swapchain. + // Once submitted, they are re-used as soon as their respective fence signals that + // they have finished rendering. + const size_t getVirtualFramesCount(); + + const std::shared_ptr<::vk::PipelineCache>& getPipelineCache(); + + // return default renderpass - a renderpass which has one color buffer and one depth buffer + const std::shared_ptr<::vk::RenderPass>& getDefaultRenderpass(); + +private: + + ofRectangle mViewport; + + void postSetup( ofEventArgs & args ); + + void setupSwapChain(); + void setupDepthStencil(); + void setupDefaultContext(); + void setupStagingContext(); + + + // vector of queues - the queue index is based on the index of the queue creation request + // it is assumed that queue 0 is graphics capable. + std::vector<::vk::Queue> mQueues; + std::vector mQueueMutex; + + // Depth buffer format + // Depth format is selected during Vulkan initialization, in createDevice() + ::vk::Format mDepthFormat; + + // our depth stencil: + // we only need one since there is only ever one frame in flight. + struct DepthStencilResource + { + ::vk::Image image = nullptr; + ::vk::DeviceMemory mem = nullptr; + ::vk::ImageView view = nullptr; + }; + + // Depthstencil image used for depth tests with default context. + std::unique_ptr> mDepthStencil; + + // vulkan swapchain + std::shared_ptr mSwapchain; + + // The default context + // This is the context which will render to the swapchain + std::shared_ptr mDefaultContext; + + // The staging context + // This Context is used to stage resources such as texture data so that it can be + // transferred to device local memory - note that staging context will get submitted before first draw. + std::shared_ptr mStagingContext; + + + // default render pass - + std::shared_ptr<::vk::RenderPass> mDefaultRenderPass; + +public: + + const ::vk::Instance& getInstance(); + + void setSwapchain( std::shared_ptr swapchain_ ){ + mSwapchain = swapchain_; + }; + + std::shared_ptr<::vk::RenderPass> generateDefaultRenderPass(::vk::Format colorFormat_, ::vk::Format depthFormat_) const; + + const std::shared_ptr & getDefaultContext(); + + const std::shared_ptr & getStagingContext(); + + void setDefaultContext( std::shared_ptr ctx ); + + std::shared_ptr & getSwapchain(); + + const of::vk::RendererProperties & getVkRendererProperties(); + + const ::vk::Format& getVkDepthFormat(); + + void submit( size_t queueIndex, ::vk::ArrayProxy&& submits, const ::vk::Fence& fence ); + + const ::vk::ImageView& getDepthStencilImageView(); + +}; + +// ---------------------------------------------------------------------- +// inline methods + +inline const ::vk::Device& ofVkRenderer::getVkDevice() const{ + return mDevice; +}; + +inline const ::vk::PhysicalDeviceProperties& ofVkRenderer::getVkPhysicalDeviceProperties() const{ + return mPhysicalDeviceProperties; +} + +inline const::vk::PhysicalDeviceMemoryProperties & ofVkRenderer::getVkPhysicalDeviceMemoryProperties() const{ + return mPhysicalDeviceMemoryProperties; +} + + +inline const size_t ofVkRenderer::getVirtualFramesCount(){ + return mSettings.numVirtualFrames; +} + +inline const std::shared_ptr& ofVkRenderer::getDefaultContext(){ + return mDefaultContext; +} + +inline const std::shared_ptr& ofVkRenderer::getStagingContext(){ + return mStagingContext; +} + +inline void ofVkRenderer::setDefaultContext( std::shared_ptr ctx ){ + mDefaultContext = ctx; +} + +inline std::shared_ptr& ofVkRenderer::getSwapchain(){ + return mSwapchain; +} + +inline const of::vk::RendererProperties & ofVkRenderer::getVkRendererProperties(){ + return mRendererProperties; +} + +inline const::vk::Format & ofVkRenderer::getVkDepthFormat(){ + return mDepthFormat; +} + +inline const std::shared_ptr<::vk::RenderPass>& ofVkRenderer::getDefaultRenderpass(){ + return mDefaultRenderPass; +} + +inline const ::vk::ImageView & ofVkRenderer::getDepthStencilImageView(){ + return mDepthStencil->view; +} + + +// ---------------------------------------------------------------------- +// clean up macros +#ifdef RENDERER_FUN_NOT_IMPLEMENTED + #undef RENDERER_FUN_NOT_IMPLEMENTED +#endif diff --git a/libs/openFrameworks/vk/ofVkRendererImpl.cpp b/libs/openFrameworks/vk/ofVkRendererImpl.cpp new file mode 100644 index 00000000000..c0f246a4f0a --- /dev/null +++ b/libs/openFrameworks/vk/ofVkRendererImpl.cpp @@ -0,0 +1,349 @@ +#include "vk/ofVkRenderer.h" +#include "vk/Shader.h" +#include "vk/RenderBatch.h" + +#include +#include + +using namespace std; + +// ---------------------------------------------------------------------- + +void ofVkRenderer::setup(){ + + mSwapchain->setRendererProperties( mRendererProperties ); + setupSwapChain(); + + mViewport = { 0.f, 0.f, float( mSwapchain->getWidth() ), float( mSwapchain->getHeight()) }; + + + // sets up resources to keep track of production frames + setupDefaultContext(); + + if ( !mDefaultRenderPass){ + mDefaultRenderPass = generateDefaultRenderPass( mSwapchain->getColorFormat(), mDepthFormat ); + } + +} + +// ---------------------------------------------------------------------- + +void ofVkRenderer::setupStagingContext(){ + + of::vk::Context::Settings settings; + + settings.transientMemoryAllocatorSettings.device = mDevice; + settings.transientMemoryAllocatorSettings.frameCount = mSettings.numVirtualFrames; + settings.transientMemoryAllocatorSettings.physicalDeviceMemoryProperties = mPhysicalDeviceMemoryProperties; + settings.transientMemoryAllocatorSettings.physicalDeviceProperties = mPhysicalDeviceProperties; + settings.transientMemoryAllocatorSettings.size = ( ( 1ULL << 24 ) * mSettings.numVirtualFrames ); + settings.renderer = this; + settings.pipelineCache = nullptr; + settings.renderToSwapChain = false; + + mStagingContext = make_shared( std::move( settings ) ); + mStagingContext->setup(); +} + +// ---------------------------------------------------------------------- + +void ofVkRenderer::setupDefaultContext(){ + + of::vk::Context::Settings settings; + + settings.transientMemoryAllocatorSettings.device = mDevice; + settings.transientMemoryAllocatorSettings.frameCount = mSettings.numVirtualFrames ; + settings.transientMemoryAllocatorSettings.physicalDeviceMemoryProperties = mPhysicalDeviceMemoryProperties ; + settings.transientMemoryAllocatorSettings.physicalDeviceProperties = mPhysicalDeviceProperties ; + settings.transientMemoryAllocatorSettings.size = ( ( 1ULL << 24 ) * mSettings.numVirtualFrames ); + settings.renderer = this; + settings.pipelineCache = getPipelineCache(); + settings.renderToSwapChain = true; + + mDefaultContext = make_shared(std::move(settings)); + mDefaultContext->setup(); +} + +// ---------------------------------------------------------------------- + + +void ofVkRenderer::setupSwapChain(){ + + // This method is called on initialisation, and + // every time the window is resized, as a resize + // means render image targets have to be re-created. + + mSwapchain->setup( ); + + setupDepthStencil(); +} + +// ---------------------------------------------------------------------- + + +void ofVkRenderer::resizeScreen( int w, int h ){ + ofLogVerbose() << "Screen resize requested."; + + // Note: this needs to halt any multi-threaded operations + // or wait for all of them to finish. + + auto err = vkDeviceWaitIdle( mDevice ); + assert( !err ); + + mSwapchain->changeExtent( w, h ); + setupSwapChain(); + + mViewport.setWidth( mSwapchain->getWidth() ); + mViewport.setHeight( mSwapchain->getHeight() ); + + ofLogVerbose() << "Screen resize complete"; +} + +// ---------------------------------------------------------------------- + +const std::shared_ptr<::vk::PipelineCache>& ofVkRenderer::getPipelineCache(){ + if ( mPipelineCache.get() == nullptr ){ + mPipelineCache = of::vk::createPipelineCache( mDevice, "pipelineCache.bin" ); + ofLog() << "Created default pipeline cache"; + } + return mPipelineCache; +} + + +// ---------------------------------------------------------------------- + +void ofVkRenderer::setupDepthStencil(){ + + vk::ImageCreateInfo imgCreateInfo; + + imgCreateInfo + .setImageType( vk::ImageType::e2D ) + .setFormat( mDepthFormat ) + .setExtent( { mSwapchain->getWidth(), mSwapchain->getHeight(), 1 } ) + .setMipLevels( 1 ) + .setArrayLayers( 1 ) + .setSamples( vk::SampleCountFlagBits::e1 ) + .setTiling( vk::ImageTiling::eOptimal ) + .setUsage( vk::ImageUsageFlags( vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eTransferSrc ) ) + .setSharingMode( vk::SharingMode::eExclusive ) + .setQueueFamilyIndexCount( 0 ) + .setInitialLayout( vk::ImageLayout::eUndefined ); + + vk::ImageViewCreateInfo imgViewCreateInfo; + + vk::ImageSubresourceRange subresourceRange; + + subresourceRange + .setAspectMask( vk::ImageAspectFlags( vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil ) ) + .setBaseMipLevel( 0 ) + .setLevelCount( 1 ) + .setBaseArrayLayer( 0 ) + .setLayerCount( 1 ); + + imgViewCreateInfo + .setViewType( vk::ImageViewType::e2D ) + .setFormat( mDepthFormat ) + .setSubresourceRange( subresourceRange ); + + mDepthStencil.reset(); + + mDepthStencil = decltype( mDepthStencil )( new DepthStencilResource, [device = mDevice]( DepthStencilResource* depthStencil ){ + + // custom deleter for depthStencil. + + if ( depthStencil->image ){ + // Destroy previously created image, if any + device.destroyImage( depthStencil->image ); + depthStencil->image = nullptr; + } + if ( depthStencil->mem ){ + // Free any previously allocated memory + device.freeMemory( depthStencil->mem ); + depthStencil->mem = nullptr; + } + if ( depthStencil->view ){ + // Destroy any previous depthStencil ImageView + device.destroyImageView( depthStencil->view ); + depthStencil->view = nullptr; + } + delete ( depthStencil ); + } ); + + { + vk::MemoryRequirements memReqs; + + mDepthStencil->image = mDevice.createImage( imgCreateInfo ); + + memReqs = mDevice.getImageMemoryRequirements( mDepthStencil->image ); + + vk::MemoryAllocateInfo memInfo; + of::vk::getMemoryAllocationInfo( memReqs, + vk::MemoryPropertyFlags( vk::MemoryPropertyFlagBits::eDeviceLocal ), + mPhysicalDeviceMemoryProperties, + memInfo ); + + mDepthStencil->mem = mDevice.allocateMemory( memInfo ); + mDevice.bindImageMemory( mDepthStencil->image, mDepthStencil->mem, 0 ); + + // now attach the newly minted image to the image view + imgViewCreateInfo.setImage( mDepthStencil->image ); + + mDepthStencil->view = mDevice.createImageView( imgViewCreateInfo, nullptr ); + + } + +} + +// ---------------------------------------------------------------------- + +std::shared_ptr<::vk::RenderPass> ofVkRenderer::generateDefaultRenderPass(::vk::Format colorFormat_, ::vk::Format depthFormat_) const { + + // Note that we keep initialLayout of the color attachment eUndefined == + // `VK_IMAGE_LAYOUT_UNDEFINED` -- we do this to say we effectively don't care + // about the initial layout and contents of (swapchain) images which + // are attached here. See also: + // http://stackoverflow.com/questions/37524032/how-to-deal-with-the-layouts-of-presentable-images + // + // We might re-investigate this and pre-transfer images to COLOR_OPTIMAL, but only on initial use, + // if we wanted to be able to accumulate drawing into this buffer. + + std::array attachments; + + attachments[0] // color attachment + .setFormat ( colorFormat_ ) + .setSamples ( vk::SampleCountFlagBits::e1 ) + .setLoadOp ( vk::AttachmentLoadOp::eClear ) + .setStoreOp ( vk::AttachmentStoreOp::eStore ) + .setStencilLoadOp ( vk::AttachmentLoadOp::eDontCare ) + .setStencilStoreOp ( vk::AttachmentStoreOp::eDontCare ) + .setInitialLayout ( vk::ImageLayout::eUndefined ) + .setFinalLayout ( vk::ImageLayout::ePresentSrcKHR ) + ; + attachments[1] //depth stencil attachment + .setFormat ( depthFormat_ ) + .setSamples ( vk::SampleCountFlagBits::e1 ) + .setLoadOp ( vk::AttachmentLoadOp::eClear ) + .setStoreOp ( vk::AttachmentStoreOp::eStore) + .setStencilLoadOp ( vk::AttachmentLoadOp::eDontCare ) + .setStencilStoreOp ( vk::AttachmentStoreOp::eDontCare ) + .setInitialLayout ( vk::ImageLayout::eUndefined ) + .setFinalLayout ( vk::ImageLayout::eDepthStencilAttachmentOptimal ) + ; + + // Define 2 attachments, and tell us what layout to expect these to be in. + // Index references attachments from above. + + vk::AttachmentReference colorReference{ 0, vk::ImageLayout::eColorAttachmentOptimal }; + vk::AttachmentReference depthReference{ 1, vk::ImageLayout::eDepthStencilAttachmentOptimal}; + + vk::SubpassDescription subpassDescription; + subpassDescription + .setPipelineBindPoint ( vk::PipelineBindPoint::eGraphics ) + .setInputAttachmentCount (0) + .setPInputAttachments (nullptr) + .setColorAttachmentCount ( 1 ) + .setPColorAttachments ( &colorReference ) + .setPResolveAttachments (nullptr) + .setPDepthStencilAttachment ( &depthReference ) + .setPPreserveAttachments (nullptr) + .setPreserveAttachmentCount (0) + ; + + // Define 2 self-dependencies for subpass 0 + + std::array dependencies; + dependencies[0] + .setSrcSubpass ( VK_SUBPASS_EXTERNAL ) // producer + .setDstSubpass ( 0 ) // consumer + .setSrcStageMask ( vk::PipelineStageFlagBits::eBottomOfPipe ) + .setDstStageMask ( vk::PipelineStageFlagBits::eColorAttachmentOutput ) + .setSrcAccessMask ( vk::AccessFlagBits::eMemoryRead ) + .setDstAccessMask ( vk::AccessFlagBits::eColorAttachmentWrite ) + .setDependencyFlags ( vk::DependencyFlagBits::eByRegion ) + ; + dependencies[1] + .setSrcSubpass ( 0 ) // producer (last possible subpass) + .setDstSubpass ( VK_SUBPASS_EXTERNAL ) // consumer + .setSrcStageMask ( vk::PipelineStageFlagBits::eColorAttachmentOutput ) + .setDstStageMask ( vk::PipelineStageFlagBits::eBottomOfPipe ) + .setSrcAccessMask ( vk::AccessFlagBits::eColorAttachmentWrite ) + .setDstAccessMask ( vk::AccessFlagBits::eMemoryRead ) + .setDependencyFlags ( vk::DependencyFlagBits::eByRegion ) + ; + + // Define 1 renderpass with 1 subpass + + vk::RenderPassCreateInfo renderPassCreateInfo; + renderPassCreateInfo + .setAttachmentCount ( attachments.size() ) + .setPAttachments ( attachments.data() ) + .setSubpassCount ( 1 ) + .setPSubpasses ( &subpassDescription ) + .setDependencyCount ( dependencies.size() ) + .setPDependencies ( dependencies.data() ); + + std::shared_ptr<::vk::RenderPass> renderPassPtr = + std::shared_ptr<::vk::RenderPass>( new ::vk::RenderPass(mDevice.createRenderPass( renderPassCreateInfo )), + [device=mDevice] (::vk::RenderPass * lhs){ + device.destroyRenderPass( *lhs ); + delete lhs; + } ); + + return renderPassPtr; +} + +// ---------------------------------------------------------------------- + +void ofVkRenderer::startRender(){ + + // start of new frame + mStagingContext->begin(); + mDefaultContext->begin(); + + // ----------| invariant: last frame has finished rendering. + + uint32_t swapIdx = 0; /*receives index of current swap chain image*/ + + // Receive index for next available swapchain image. + // Effectively, ownership of the image is transferred from the swapchain to the context. + // + // Ownership is transferred async, only once semaphorePresentComplete was signalled. + // The swapchain will signal the semaphore as soon as the image is ready to be written into. + // + // This means, a queue submission from the context that draws into the image + // must wait for this semaphore. + auto err = mSwapchain->acquireNextImage( mDefaultContext->getSemaphoreWait(), swapIdx ); + + // ---------| invariant: new swap chain image has been acquired for drawing into. + + // connect default context frame buffer to swapchain image, and depth stencil image + mDefaultContext->setSwapchainImageView( mSwapchain->getImage( swapIdx ).view ); + +} + +// ---------------------------------------------------------------------- + +void ofVkRenderer::finishRender(){ + + // TODO: if there are other Contexts flying around on other threads, + // ask them to finish their work for the frame. + mStagingContext->end(); + mDefaultContext->end(); + + // present swapchain frame + mSwapchain->queuePresent( mQueues[0], mQueueMutex[0], { mDefaultContext->getSemaphoreSignalOnComplete()} ); + +} + +// ---------------------------------------------------------------------- + +void ofVkRenderer::submit( size_t queueIndex, ::vk::ArrayProxy&& submits,const ::vk::Fence& fence ){ + std::lock_guard lock{ mQueueMutex[queueIndex] }; + mQueues[queueIndex].submit( submits, fence ); +} + +// ---------------------------------------------------------------------- + +const uint32_t ofVkRenderer::getSwapChainSize(){ + return mSwapchain->getImageCount(); +} diff --git a/libs/openFrameworks/vk/shaders/default.vert b/libs/openFrameworks/vk/shaders/default.vert new file mode 100644 index 00000000000..589048e91b2 --- /dev/null +++ b/libs/openFrameworks/vk/shaders/default.vert @@ -0,0 +1,47 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// uniforms (resources) +layout (set = 0, binding = 0) uniform DefaultMatrices +{ + mat4 projectionMatrix; + mat4 modelMatrix; + mat4 viewMatrix; +}; + +layout (set = 0, binding = 1) uniform Style +{ + vec4 globalColor; +} style; + +// inputs (vertex attributes) +layout ( location = 0 ) in vec3 inPos; +layout ( location = 1 ) in vec3 inNormal; +layout ( location = 2 ) in vec2 inTexCoord; + +// output (to next shader stage) +layout ( location = 0 ) out vec4 outPos; +layout ( location = 1 ) out vec3 outNormal; +layout ( location = 2 ) out vec2 outTexCoord; + +// we override the built-in fixed function outputs +// to have more control over the SPIR-V code created. +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main() +{ + // Use globalColor so compiler does not remove it automatically. + vec4 colorSink = style.globalColor * 1; + mat4 modelViewMatrix = viewMatrix * modelMatrix; + + outTexCoord = inTexCoord; + outNormal = (transpose(inverse(modelViewMatrix)) * vec4(inNormal,1.f)).xyz; + outPos = modelViewMatrix * vec4(inPos.xyz, 1.0); + + gl_Position = projectionMatrix * outPos; +} diff --git a/libs/openFrameworks/vk/shaders/default.vert.spv b/libs/openFrameworks/vk/shaders/default.vert.spv new file mode 100644 index 00000000000..23158898404 --- /dev/null +++ b/libs/openFrameworks/vk/shaders/default.vert.spv @@ -0,0 +1,149 @@ +0x07230203,0x00010000,0x000d0001,0x00000049, +0x00000000,0x00020011,0x00000001,0x0006000b, +0x00000001,0x4c534c47,0x6474732e,0x3035342e, +0x00000000,0x0003000e,0x00000000,0x00000001, +0x000c000f,0x00000000,0x00000004,0x6e69616d, +0x00000000,0x00000024,0x00000026,0x0000002a, +0x0000002f,0x00000038,0x0000003a,0x00000043, +0x00030003,0x00000002,0x000001c2,0x00090004, +0x415f4c47,0x735f4252,0x72617065,0x5f657461, +0x64616873,0x6f5f7265,0x63656a62,0x00007374, +0x00090004,0x415f4c47,0x735f4252,0x69646168, +0x6c5f676e,0x75676e61,0x5f656761,0x70303234, +0x006b6361,0x000a0004,0x475f4c47,0x4c474f4f, +0x70635f45,0x74735f70,0x5f656c79,0x656e696c, +0x7269645f,0x69746365,0x00006576,0x00080004, +0x475f4c47,0x4c474f4f,0x6e695f45,0x64756c63, +0x69645f65,0x74636572,0x00657669,0x00040005, +0x00000004,0x6e69616d,0x00000000,0x00050005, +0x00000009,0x6f6c6f63,0x6e695372,0x0000006b, +0x00040005,0x0000000a,0x6c797453,0x00000065, +0x00060006,0x0000000a,0x00000000,0x626f6c67, +0x6f436c61,0x00726f6c,0x00040005,0x0000000c, +0x6c797473,0x00000065,0x00060005,0x00000016, +0x65646f6d,0x6569566c,0x74614d77,0x00786972, +0x00060005,0x00000017,0x61666544,0x4d746c75, +0x69727461,0x00736563,0x00080006,0x00000017, +0x00000000,0x6a6f7270,0x69746365,0x614d6e6f, +0x78697274,0x00000000,0x00060006,0x00000017, +0x00000001,0x65646f6d,0x74614d6c,0x00786972, +0x00060006,0x00000017,0x00000002,0x77656976, +0x7274614d,0x00007869,0x00030005,0x00000019, +0x00000000,0x00050005,0x00000024,0x5474756f, +0x6f437865,0x0064726f,0x00050005,0x00000026, +0x65546e69,0x6f6f4378,0x00006472,0x00050005, +0x0000002a,0x4e74756f,0x616d726f,0x0000006c, +0x00050005,0x0000002f,0x6f4e6e69,0x6c616d72, +0x00000000,0x00040005,0x00000038,0x5074756f, +0x0000736f,0x00040005,0x0000003a,0x6f506e69, +0x00000073,0x00060005,0x00000041,0x505f6c67, +0x65567265,0x78657472,0x00000000,0x00060006, +0x00000041,0x00000000,0x505f6c67,0x7469736f, +0x006e6f69,0x00030005,0x00000043,0x00000000, +0x00050048,0x0000000a,0x00000000,0x00000023, +0x00000000,0x00030047,0x0000000a,0x00000002, +0x00040047,0x0000000c,0x00000022,0x00000000, +0x00040047,0x0000000c,0x00000021,0x00000001, +0x00040048,0x00000017,0x00000000,0x00000005, +0x00050048,0x00000017,0x00000000,0x00000023, +0x00000000,0x00050048,0x00000017,0x00000000, +0x00000007,0x00000010,0x00040048,0x00000017, +0x00000001,0x00000005,0x00050048,0x00000017, +0x00000001,0x00000023,0x00000040,0x00050048, +0x00000017,0x00000001,0x00000007,0x00000010, +0x00040048,0x00000017,0x00000002,0x00000005, +0x00050048,0x00000017,0x00000002,0x00000023, +0x00000080,0x00050048,0x00000017,0x00000002, +0x00000007,0x00000010,0x00030047,0x00000017, +0x00000002,0x00040047,0x00000019,0x00000022, +0x00000000,0x00040047,0x00000019,0x00000021, +0x00000000,0x00040047,0x00000024,0x0000001e, +0x00000002,0x00040047,0x00000026,0x0000001e, +0x00000002,0x00040047,0x0000002a,0x0000001e, +0x00000001,0x00040047,0x0000002f,0x0000001e, +0x00000001,0x00040047,0x00000038,0x0000001e, +0x00000000,0x00040047,0x0000003a,0x0000001e, +0x00000000,0x00050048,0x00000041,0x00000000, +0x0000000b,0x00000000,0x00030047,0x00000041, +0x00000002,0x00020013,0x00000002,0x00030021, +0x00000003,0x00000002,0x00030016,0x00000006, +0x00000020,0x00040017,0x00000007,0x00000006, +0x00000004,0x00040020,0x00000008,0x00000007, +0x00000007,0x0003001e,0x0000000a,0x00000007, +0x00040020,0x0000000b,0x00000002,0x0000000a, +0x0004003b,0x0000000b,0x0000000c,0x00000002, +0x00040015,0x0000000d,0x00000020,0x00000001, +0x0004002b,0x0000000d,0x0000000e,0x00000000, +0x00040020,0x0000000f,0x00000002,0x00000007, +0x0004002b,0x00000006,0x00000012,0x3f800000, +0x00040018,0x00000014,0x00000007,0x00000004, +0x00040020,0x00000015,0x00000007,0x00000014, +0x0005001e,0x00000017,0x00000014,0x00000014, +0x00000014,0x00040020,0x00000018,0x00000002, +0x00000017,0x0004003b,0x00000018,0x00000019, +0x00000002,0x0004002b,0x0000000d,0x0000001a, +0x00000002,0x00040020,0x0000001b,0x00000002, +0x00000014,0x0004002b,0x0000000d,0x0000001e, +0x00000001,0x00040017,0x00000022,0x00000006, +0x00000002,0x00040020,0x00000023,0x00000003, +0x00000022,0x0004003b,0x00000023,0x00000024, +0x00000003,0x00040020,0x00000025,0x00000001, +0x00000022,0x0004003b,0x00000025,0x00000026, +0x00000001,0x00040017,0x00000028,0x00000006, +0x00000003,0x00040020,0x00000029,0x00000003, +0x00000028,0x0004003b,0x00000029,0x0000002a, +0x00000003,0x00040020,0x0000002e,0x00000001, +0x00000028,0x0004003b,0x0000002e,0x0000002f, +0x00000001,0x00040020,0x00000037,0x00000003, +0x00000007,0x0004003b,0x00000037,0x00000038, +0x00000003,0x0004003b,0x0000002e,0x0000003a, +0x00000001,0x0003001e,0x00000041,0x00000007, +0x00040020,0x00000042,0x00000003,0x00000041, +0x0004003b,0x00000042,0x00000043,0x00000003, +0x00050036,0x00000002,0x00000004,0x00000000, +0x00000003,0x000200f8,0x00000005,0x0004003b, +0x00000008,0x00000009,0x00000007,0x0004003b, +0x00000015,0x00000016,0x00000007,0x00050041, +0x0000000f,0x00000010,0x0000000c,0x0000000e, +0x0004003d,0x00000007,0x00000011,0x00000010, +0x0005008e,0x00000007,0x00000013,0x00000011, +0x00000012,0x0003003e,0x00000009,0x00000013, +0x00050041,0x0000001b,0x0000001c,0x00000019, +0x0000001a,0x0004003d,0x00000014,0x0000001d, +0x0000001c,0x00050041,0x0000001b,0x0000001f, +0x00000019,0x0000001e,0x0004003d,0x00000014, +0x00000020,0x0000001f,0x00050092,0x00000014, +0x00000021,0x0000001d,0x00000020,0x0003003e, +0x00000016,0x00000021,0x0004003d,0x00000022, +0x00000027,0x00000026,0x0003003e,0x00000024, +0x00000027,0x0004003d,0x00000014,0x0000002b, +0x00000016,0x0006000c,0x00000014,0x0000002c, +0x00000001,0x00000022,0x0000002b,0x00040054, +0x00000014,0x0000002d,0x0000002c,0x0004003d, +0x00000028,0x00000030,0x0000002f,0x00050051, +0x00000006,0x00000031,0x00000030,0x00000000, +0x00050051,0x00000006,0x00000032,0x00000030, +0x00000001,0x00050051,0x00000006,0x00000033, +0x00000030,0x00000002,0x00070050,0x00000007, +0x00000034,0x00000031,0x00000032,0x00000033, +0x00000012,0x00050091,0x00000007,0x00000035, +0x0000002d,0x00000034,0x0008004f,0x00000028, +0x00000036,0x00000035,0x00000035,0x00000000, +0x00000001,0x00000002,0x0003003e,0x0000002a, +0x00000036,0x0004003d,0x00000014,0x00000039, +0x00000016,0x0004003d,0x00000028,0x0000003b, +0x0000003a,0x00050051,0x00000006,0x0000003c, +0x0000003b,0x00000000,0x00050051,0x00000006, +0x0000003d,0x0000003b,0x00000001,0x00050051, +0x00000006,0x0000003e,0x0000003b,0x00000002, +0x00070050,0x00000007,0x0000003f,0x0000003c, +0x0000003d,0x0000003e,0x00000012,0x00050091, +0x00000007,0x00000040,0x00000039,0x0000003f, +0x0003003e,0x00000038,0x00000040,0x00050041, +0x0000001b,0x00000044,0x00000019,0x0000000e, +0x0004003d,0x00000014,0x00000045,0x00000044, +0x0004003d,0x00000007,0x00000046,0x00000038, +0x00050091,0x00000007,0x00000047,0x00000045, +0x00000046,0x00050041,0x00000037,0x00000048, +0x00000043,0x0000000e,0x0003003e,0x00000048, +0x00000047,0x000100fd,0x00010038 diff --git a/libs/openFrameworks/vk/shaders/default_global_color.frag b/libs/openFrameworks/vk/shaders/default_global_color.frag new file mode 100644 index 00000000000..5ef96a16674 --- /dev/null +++ b/libs/openFrameworks/vk/shaders/default_global_color.frag @@ -0,0 +1,30 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// uniforms (resources) +layout (set = 0, binding = 0) uniform DefaultMatrices +{ + mat4 projectionMatrix; + mat4 modelMatrix; + mat4 viewMatrix; +}; + +layout (set = 0, binding = 1) uniform Style +{ + vec4 globalColor; +} style; + +// inputs +layout ( location = 0 ) in vec4 inPos; +layout ( location = 1 ) in vec3 inNormal; +layout ( location = 2 ) in vec2 inTexCoord; + +// outputs +layout ( location = 0 ) out vec4 outFragColor; + +void main() +{ + outFragColor = style.globalColor; +} \ No newline at end of file diff --git a/libs/openFrameworks/vk/shaders/default_global_color.frag.spv b/libs/openFrameworks/vk/shaders/default_global_color.frag.spv new file mode 100644 index 00000000000..64f715f5a98 --- /dev/null +++ b/libs/openFrameworks/vk/shaders/default_global_color.frag.spv @@ -0,0 +1,83 @@ +0x07230203,0x00010000,0x000d0001,0x0000001e, +0x00000000,0x00020011,0x00000001,0x0006000b, +0x00000001,0x4c534c47,0x6474732e,0x3035342e, +0x00000000,0x0003000e,0x00000000,0x00000001, +0x0009000f,0x00000004,0x00000004,0x6e69616d, +0x00000000,0x00000009,0x00000017,0x0000001a, +0x0000001d,0x00030010,0x00000004,0x00000007, +0x00030003,0x00000002,0x000001c2,0x00090004, +0x415f4c47,0x735f4252,0x72617065,0x5f657461, +0x64616873,0x6f5f7265,0x63656a62,0x00007374, +0x00090004,0x415f4c47,0x735f4252,0x69646168, +0x6c5f676e,0x75676e61,0x5f656761,0x70303234, +0x006b6361,0x000a0004,0x475f4c47,0x4c474f4f, +0x70635f45,0x74735f70,0x5f656c79,0x656e696c, +0x7269645f,0x69746365,0x00006576,0x00080004, +0x475f4c47,0x4c474f4f,0x6e695f45,0x64756c63, +0x69645f65,0x74636572,0x00657669,0x00040005, +0x00000004,0x6e69616d,0x00000000,0x00060005, +0x00000009,0x4674756f,0x43676172,0x726f6c6f, +0x00000000,0x00040005,0x0000000a,0x6c797453, +0x00000065,0x00060006,0x0000000a,0x00000000, +0x626f6c67,0x6f436c61,0x00726f6c,0x00040005, +0x0000000c,0x6c797473,0x00000065,0x00060005, +0x00000013,0x61666544,0x4d746c75,0x69727461, +0x00736563,0x00080006,0x00000013,0x00000000, +0x6a6f7270,0x69746365,0x614d6e6f,0x78697274, +0x00000000,0x00060006,0x00000013,0x00000001, +0x65646f6d,0x74614d6c,0x00786972,0x00060006, +0x00000013,0x00000002,0x77656976,0x7274614d, +0x00007869,0x00030005,0x00000015,0x00000000, +0x00040005,0x00000017,0x6f506e69,0x00000073, +0x00050005,0x0000001a,0x6f4e6e69,0x6c616d72, +0x00000000,0x00050005,0x0000001d,0x65546e69, +0x6f6f4378,0x00006472,0x00040047,0x00000009, +0x0000001e,0x00000000,0x00050048,0x0000000a, +0x00000000,0x00000023,0x00000000,0x00030047, +0x0000000a,0x00000002,0x00040047,0x0000000c, +0x00000022,0x00000000,0x00040047,0x0000000c, +0x00000021,0x00000001,0x00040048,0x00000013, +0x00000000,0x00000005,0x00050048,0x00000013, +0x00000000,0x00000023,0x00000000,0x00050048, +0x00000013,0x00000000,0x00000007,0x00000010, +0x00040048,0x00000013,0x00000001,0x00000005, +0x00050048,0x00000013,0x00000001,0x00000023, +0x00000040,0x00050048,0x00000013,0x00000001, +0x00000007,0x00000010,0x00040048,0x00000013, +0x00000002,0x00000005,0x00050048,0x00000013, +0x00000002,0x00000023,0x00000080,0x00050048, +0x00000013,0x00000002,0x00000007,0x00000010, +0x00030047,0x00000013,0x00000002,0x00040047, +0x00000015,0x00000022,0x00000000,0x00040047, +0x00000015,0x00000021,0x00000000,0x00040047, +0x00000017,0x0000001e,0x00000000,0x00040047, +0x0000001a,0x0000001e,0x00000001,0x00040047, +0x0000001d,0x0000001e,0x00000002,0x00020013, +0x00000002,0x00030021,0x00000003,0x00000002, +0x00030016,0x00000006,0x00000020,0x00040017, +0x00000007,0x00000006,0x00000004,0x00040020, +0x00000008,0x00000003,0x00000007,0x0004003b, +0x00000008,0x00000009,0x00000003,0x0003001e, +0x0000000a,0x00000007,0x00040020,0x0000000b, +0x00000002,0x0000000a,0x0004003b,0x0000000b, +0x0000000c,0x00000002,0x00040015,0x0000000d, +0x00000020,0x00000001,0x0004002b,0x0000000d, +0x0000000e,0x00000000,0x00040020,0x0000000f, +0x00000002,0x00000007,0x00040018,0x00000012, +0x00000007,0x00000004,0x0005001e,0x00000013, +0x00000012,0x00000012,0x00000012,0x00040020, +0x00000014,0x00000002,0x00000013,0x0004003b, +0x00000014,0x00000015,0x00000002,0x00040020, +0x00000016,0x00000001,0x00000007,0x0004003b, +0x00000016,0x00000017,0x00000001,0x00040017, +0x00000018,0x00000006,0x00000003,0x00040020, +0x00000019,0x00000001,0x00000018,0x0004003b, +0x00000019,0x0000001a,0x00000001,0x00040017, +0x0000001b,0x00000006,0x00000002,0x00040020, +0x0000001c,0x00000001,0x0000001b,0x0004003b, +0x0000001c,0x0000001d,0x00000001,0x00050036, +0x00000002,0x00000004,0x00000000,0x00000003, +0x000200f8,0x00000005,0x00050041,0x0000000f, +0x00000010,0x0000000c,0x0000000e,0x0004003d, +0x00000007,0x00000011,0x00000010,0x0003003e, +0x00000009,0x00000011,0x000100fd,0x00010038 diff --git a/libs/openFrameworks/vk/shaders/default_normal_color.frag b/libs/openFrameworks/vk/shaders/default_normal_color.frag new file mode 100644 index 00000000000..20f05d83e24 --- /dev/null +++ b/libs/openFrameworks/vk/shaders/default_normal_color.frag @@ -0,0 +1,31 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// uniforms (resources) +layout (set = 0, binding = 0) uniform DefaultMatrices +{ + mat4 projectionMatrix; + mat4 modelMatrix; + mat4 viewMatrix; +}; + +layout (set = 0, binding = 1) uniform Style +{ + vec4 globalColor; +} style; + +// inputs +layout ( location = 0 ) in vec4 inPos; +layout ( location = 1 ) in vec3 inNormal; +layout ( location = 2 ) in vec2 inTexCoord; + +// outputs +layout ( location = 0 ) out vec4 outFragColor; + +void main() +{ + vec4 normalColor = vec4(0.5 * (inNormal + vec3(1)),1.); + outFragColor = normalColor; +} \ No newline at end of file diff --git a/libs/openFrameworks/vk/shaders/default_normal_color.frag.spv b/libs/openFrameworks/vk/shaders/default_normal_color.frag.spv new file mode 100644 index 00000000000..3dfb6ff510d --- /dev/null +++ b/libs/openFrameworks/vk/shaders/default_normal_color.frag.spv @@ -0,0 +1,96 @@ +0x07230203,0x00010000,0x000d0001,0x00000026, +0x00000000,0x00020011,0x00000001,0x0006000b, +0x00000001,0x4c534c47,0x6474732e,0x3035342e, +0x00000000,0x0003000e,0x00000000,0x00000001, +0x0009000f,0x00000004,0x00000004,0x6e69616d, +0x00000000,0x0000000d,0x00000018,0x00000022, +0x00000025,0x00030010,0x00000004,0x00000007, +0x00030003,0x00000002,0x000001c2,0x00090004, +0x415f4c47,0x735f4252,0x72617065,0x5f657461, +0x64616873,0x6f5f7265,0x63656a62,0x00007374, +0x00090004,0x415f4c47,0x735f4252,0x69646168, +0x6c5f676e,0x75676e61,0x5f656761,0x70303234, +0x006b6361,0x000a0004,0x475f4c47,0x4c474f4f, +0x70635f45,0x74735f70,0x5f656c79,0x656e696c, +0x7269645f,0x69746365,0x00006576,0x00080004, +0x475f4c47,0x4c474f4f,0x6e695f45,0x64756c63, +0x69645f65,0x74636572,0x00657669,0x00040005, +0x00000004,0x6e69616d,0x00000000,0x00050005, +0x00000009,0x6d726f6e,0x6f436c61,0x00726f6c, +0x00050005,0x0000000d,0x6f4e6e69,0x6c616d72, +0x00000000,0x00060005,0x00000018,0x4674756f, +0x43676172,0x726f6c6f,0x00000000,0x00060005, +0x0000001b,0x61666544,0x4d746c75,0x69727461, +0x00736563,0x00080006,0x0000001b,0x00000000, +0x6a6f7270,0x69746365,0x614d6e6f,0x78697274, +0x00000000,0x00060006,0x0000001b,0x00000001, +0x65646f6d,0x74614d6c,0x00786972,0x00060006, +0x0000001b,0x00000002,0x77656976,0x7274614d, +0x00007869,0x00030005,0x0000001d,0x00000000, +0x00040005,0x0000001e,0x6c797453,0x00000065, +0x00060006,0x0000001e,0x00000000,0x626f6c67, +0x6f436c61,0x00726f6c,0x00040005,0x00000020, +0x6c797473,0x00000065,0x00040005,0x00000022, +0x6f506e69,0x00000073,0x00050005,0x00000025, +0x65546e69,0x6f6f4378,0x00006472,0x00040047, +0x0000000d,0x0000001e,0x00000001,0x00040047, +0x00000018,0x0000001e,0x00000000,0x00040048, +0x0000001b,0x00000000,0x00000005,0x00050048, +0x0000001b,0x00000000,0x00000023,0x00000000, +0x00050048,0x0000001b,0x00000000,0x00000007, +0x00000010,0x00040048,0x0000001b,0x00000001, +0x00000005,0x00050048,0x0000001b,0x00000001, +0x00000023,0x00000040,0x00050048,0x0000001b, +0x00000001,0x00000007,0x00000010,0x00040048, +0x0000001b,0x00000002,0x00000005,0x00050048, +0x0000001b,0x00000002,0x00000023,0x00000080, +0x00050048,0x0000001b,0x00000002,0x00000007, +0x00000010,0x00030047,0x0000001b,0x00000002, +0x00040047,0x0000001d,0x00000022,0x00000000, +0x00040047,0x0000001d,0x00000021,0x00000000, +0x00050048,0x0000001e,0x00000000,0x00000023, +0x00000000,0x00030047,0x0000001e,0x00000002, +0x00040047,0x00000020,0x00000022,0x00000000, +0x00040047,0x00000020,0x00000021,0x00000001, +0x00040047,0x00000022,0x0000001e,0x00000000, +0x00040047,0x00000025,0x0000001e,0x00000002, +0x00020013,0x00000002,0x00030021,0x00000003, +0x00000002,0x00030016,0x00000006,0x00000020, +0x00040017,0x00000007,0x00000006,0x00000004, +0x00040020,0x00000008,0x00000007,0x00000007, +0x0004002b,0x00000006,0x0000000a,0x3f000000, +0x00040017,0x0000000b,0x00000006,0x00000003, +0x00040020,0x0000000c,0x00000001,0x0000000b, +0x0004003b,0x0000000c,0x0000000d,0x00000001, +0x0004002b,0x00000006,0x0000000f,0x3f800000, +0x0006002c,0x0000000b,0x00000010,0x0000000f, +0x0000000f,0x0000000f,0x00040020,0x00000017, +0x00000003,0x00000007,0x0004003b,0x00000017, +0x00000018,0x00000003,0x00040018,0x0000001a, +0x00000007,0x00000004,0x0005001e,0x0000001b, +0x0000001a,0x0000001a,0x0000001a,0x00040020, +0x0000001c,0x00000002,0x0000001b,0x0004003b, +0x0000001c,0x0000001d,0x00000002,0x0003001e, +0x0000001e,0x00000007,0x00040020,0x0000001f, +0x00000002,0x0000001e,0x0004003b,0x0000001f, +0x00000020,0x00000002,0x00040020,0x00000021, +0x00000001,0x00000007,0x0004003b,0x00000021, +0x00000022,0x00000001,0x00040017,0x00000023, +0x00000006,0x00000002,0x00040020,0x00000024, +0x00000001,0x00000023,0x0004003b,0x00000024, +0x00000025,0x00000001,0x00050036,0x00000002, +0x00000004,0x00000000,0x00000003,0x000200f8, +0x00000005,0x0004003b,0x00000008,0x00000009, +0x00000007,0x0004003d,0x0000000b,0x0000000e, +0x0000000d,0x00050081,0x0000000b,0x00000011, +0x0000000e,0x00000010,0x0005008e,0x0000000b, +0x00000012,0x00000011,0x0000000a,0x00050051, +0x00000006,0x00000013,0x00000012,0x00000000, +0x00050051,0x00000006,0x00000014,0x00000012, +0x00000001,0x00050051,0x00000006,0x00000015, +0x00000012,0x00000002,0x00070050,0x00000007, +0x00000016,0x00000013,0x00000014,0x00000015, +0x0000000f,0x0003003e,0x00000009,0x00000016, +0x0004003d,0x00000007,0x00000019,0x00000009, +0x0003003e,0x00000018,0x00000019,0x000100fd, +0x00010038 diff --git a/libs/openFrameworks/vk/shaders/readme.md b/libs/openFrameworks/vk/shaders/readme.md new file mode 100644 index 00000000000..f9294766f42 --- /dev/null +++ b/libs/openFrameworks/vk/shaders/readme.md @@ -0,0 +1,22 @@ +# Precompiled default shaders for openFrameworks VK Renderer + +The shaders in this folder are used internally by the VK renderer. +We provide shaders as GLSL source - and SPIRV, and source and +SPIRV must be in sync. + +To compile shaders we use glslc, which is the front-end for +standalone shaderc: + + glslc default.vert default.frag -c -mfmt=num + +Note the '-mfmt=num' parameter - this stores spirv in text form +as a list uint32_t literals. + +We compile shaders into spir-v because it accelerates load times, +and also allows us to inline shader code as binary blobs into +applications: + + // Include shader source into binary + static const std::vector myShader { + #include "myshadersource.spv" + } diff --git a/libs/openFrameworks/vk/spirv-cross/include/GLSL.std.450.h b/libs/openFrameworks/vk/spirv-cross/include/GLSL.std.450.h new file mode 100644 index 00000000000..54cc00e9a88 --- /dev/null +++ b/libs/openFrameworks/vk/spirv-cross/include/GLSL.std.450.h @@ -0,0 +1,131 @@ +/* +** Copyright (c) 2014-2016 The Khronos Group Inc. +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and/or associated documentation files (the "Materials"), +** to deal in the Materials without restriction, including without limitation +** the rights to use, copy, modify, merge, publish, distribute, sublicense, +** and/or sell copies of the Materials, and to permit persons to whom the +** Materials are furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Materials. +** +** MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS +** STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND +** HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ +** +** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +** THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +** FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS +** IN THE MATERIALS. +*/ + +#ifndef GLSLstd450_H +#define GLSLstd450_H + +static const int GLSLstd450Version = 100; +static const int GLSLstd450Revision = 3; + +enum GLSLstd450 { + GLSLstd450Bad = 0, // Don't use + + GLSLstd450Round = 1, + GLSLstd450RoundEven = 2, + GLSLstd450Trunc = 3, + GLSLstd450FAbs = 4, + GLSLstd450SAbs = 5, + GLSLstd450FSign = 6, + GLSLstd450SSign = 7, + GLSLstd450Floor = 8, + GLSLstd450Ceil = 9, + GLSLstd450Fract = 10, + + GLSLstd450Radians = 11, + GLSLstd450Degrees = 12, + GLSLstd450Sin = 13, + GLSLstd450Cos = 14, + GLSLstd450Tan = 15, + GLSLstd450Asin = 16, + GLSLstd450Acos = 17, + GLSLstd450Atan = 18, + GLSLstd450Sinh = 19, + GLSLstd450Cosh = 20, + GLSLstd450Tanh = 21, + GLSLstd450Asinh = 22, + GLSLstd450Acosh = 23, + GLSLstd450Atanh = 24, + GLSLstd450Atan2 = 25, + + GLSLstd450Pow = 26, + GLSLstd450Exp = 27, + GLSLstd450Log = 28, + GLSLstd450Exp2 = 29, + GLSLstd450Log2 = 30, + GLSLstd450Sqrt = 31, + GLSLstd450InverseSqrt = 32, + + GLSLstd450Determinant = 33, + GLSLstd450MatrixInverse = 34, + + GLSLstd450Modf = 35, // second operand needs an OpVariable to write to + GLSLstd450ModfStruct = 36, // no OpVariable operand + GLSLstd450FMin = 37, + GLSLstd450UMin = 38, + GLSLstd450SMin = 39, + GLSLstd450FMax = 40, + GLSLstd450UMax = 41, + GLSLstd450SMax = 42, + GLSLstd450FClamp = 43, + GLSLstd450UClamp = 44, + GLSLstd450SClamp = 45, + GLSLstd450FMix = 46, + GLSLstd450IMix = 47, // Reserved + GLSLstd450Step = 48, + GLSLstd450SmoothStep = 49, + + GLSLstd450Fma = 50, + GLSLstd450Frexp = 51, // second operand needs an OpVariable to write to + GLSLstd450FrexpStruct = 52, // no OpVariable operand + GLSLstd450Ldexp = 53, + + GLSLstd450PackSnorm4x8 = 54, + GLSLstd450PackUnorm4x8 = 55, + GLSLstd450PackSnorm2x16 = 56, + GLSLstd450PackUnorm2x16 = 57, + GLSLstd450PackHalf2x16 = 58, + GLSLstd450PackDouble2x32 = 59, + GLSLstd450UnpackSnorm2x16 = 60, + GLSLstd450UnpackUnorm2x16 = 61, + GLSLstd450UnpackHalf2x16 = 62, + GLSLstd450UnpackSnorm4x8 = 63, + GLSLstd450UnpackUnorm4x8 = 64, + GLSLstd450UnpackDouble2x32 = 65, + + GLSLstd450Length = 66, + GLSLstd450Distance = 67, + GLSLstd450Cross = 68, + GLSLstd450Normalize = 69, + GLSLstd450FaceForward = 70, + GLSLstd450Reflect = 71, + GLSLstd450Refract = 72, + + GLSLstd450FindILsb = 73, + GLSLstd450FindSMsb = 74, + GLSLstd450FindUMsb = 75, + + GLSLstd450InterpolateAtCentroid = 76, + GLSLstd450InterpolateAtSample = 77, + GLSLstd450InterpolateAtOffset = 78, + + GLSLstd450NMin = 79, + GLSLstd450NMax = 80, + GLSLstd450NClamp = 81, + + GLSLstd450Count +}; + +#endif // #ifndef GLSLstd450_H diff --git a/libs/openFrameworks/vk/spirv-cross/include/spirv.hpp b/libs/openFrameworks/vk/spirv-cross/include/spirv.hpp new file mode 100644 index 00000000000..987f3c1d679 --- /dev/null +++ b/libs/openFrameworks/vk/spirv-cross/include/spirv.hpp @@ -0,0 +1,881 @@ +// Copyright (c) 2014-2016 The Khronos Group Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and/or associated documentation files (the "Materials"), +// to deal in the Materials without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Materials, and to permit persons to whom the +// Materials are furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Materials. +// +// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS +// STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND +// HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ +// +// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS +// IN THE MATERIALS. + +// This header is automatically generated by the same tool that creates +// the Binary Section of the SPIR-V specification. + +// Enumeration tokens for SPIR-V, in various styles: +// C, C++, C++11, JSON, Lua, Python +// +// - C will have tokens with a "Spv" prefix, e.g.: SpvSourceLanguageGLSL +// - C++ will have tokens in the "spv" name space, e.g.: spv::SourceLanguageGLSL +// - C++11 will use enum classes in the spv namespace, e.g.: spv::SourceLanguage::GLSL +// - Lua will use tables, e.g.: spv.SourceLanguage.GLSL +// - Python will use dictionaries, e.g.: spv['SourceLanguage']['GLSL'] +// +// Some tokens act like mask values, which can be OR'd together, +// while others are mutually exclusive. The mask-like ones have +// "Mask" in their name, and a parallel enum that has the shift +// amount (1 << x) for each corresponding enumerant. + +#ifndef spirv_HPP +#define spirv_HPP + +namespace spv { + +typedef unsigned int Id; + +#define SPV_VERSION 0x10000 +#define SPV_REVISION 5 + +static const unsigned int MagicNumber = 0x07230203; +static const unsigned int Version = 0x00010000; +static const unsigned int Revision = 5; +static const unsigned int OpCodeMask = 0xffff; +static const unsigned int WordCountShift = 16; + +enum SourceLanguage { + SourceLanguageUnknown = 0, + SourceLanguageESSL = 1, + SourceLanguageGLSL = 2, + SourceLanguageOpenCL_C = 3, + SourceLanguageOpenCL_CPP = 4, +}; + +enum ExecutionModel { + ExecutionModelVertex = 0, + ExecutionModelTessellationControl = 1, + ExecutionModelTessellationEvaluation = 2, + ExecutionModelGeometry = 3, + ExecutionModelFragment = 4, + ExecutionModelGLCompute = 5, + ExecutionModelKernel = 6, +}; + +enum AddressingModel { + AddressingModelLogical = 0, + AddressingModelPhysical32 = 1, + AddressingModelPhysical64 = 2, +}; + +enum MemoryModel { + MemoryModelSimple = 0, + MemoryModelGLSL450 = 1, + MemoryModelOpenCL = 2, +}; + +enum ExecutionMode { + ExecutionModeInvocations = 0, + ExecutionModeSpacingEqual = 1, + ExecutionModeSpacingFractionalEven = 2, + ExecutionModeSpacingFractionalOdd = 3, + ExecutionModeVertexOrderCw = 4, + ExecutionModeVertexOrderCcw = 5, + ExecutionModePixelCenterInteger = 6, + ExecutionModeOriginUpperLeft = 7, + ExecutionModeOriginLowerLeft = 8, + ExecutionModeEarlyFragmentTests = 9, + ExecutionModePointMode = 10, + ExecutionModeXfb = 11, + ExecutionModeDepthReplacing = 12, + ExecutionModeDepthGreater = 14, + ExecutionModeDepthLess = 15, + ExecutionModeDepthUnchanged = 16, + ExecutionModeLocalSize = 17, + ExecutionModeLocalSizeHint = 18, + ExecutionModeInputPoints = 19, + ExecutionModeInputLines = 20, + ExecutionModeInputLinesAdjacency = 21, + ExecutionModeTriangles = 22, + ExecutionModeInputTrianglesAdjacency = 23, + ExecutionModeQuads = 24, + ExecutionModeIsolines = 25, + ExecutionModeOutputVertices = 26, + ExecutionModeOutputPoints = 27, + ExecutionModeOutputLineStrip = 28, + ExecutionModeOutputTriangleStrip = 29, + ExecutionModeVecTypeHint = 30, + ExecutionModeContractionOff = 31, +}; + +enum StorageClass { + StorageClassUniformConstant = 0, + StorageClassInput = 1, + StorageClassUniform = 2, + StorageClassOutput = 3, + StorageClassWorkgroup = 4, + StorageClassCrossWorkgroup = 5, + StorageClassPrivate = 6, + StorageClassFunction = 7, + StorageClassGeneric = 8, + StorageClassPushConstant = 9, + StorageClassAtomicCounter = 10, + StorageClassImage = 11, +}; + +enum Dim { + Dim1D = 0, + Dim2D = 1, + Dim3D = 2, + DimCube = 3, + DimRect = 4, + DimBuffer = 5, + DimSubpassData = 6, +}; + +enum SamplerAddressingMode { + SamplerAddressingModeNone = 0, + SamplerAddressingModeClampToEdge = 1, + SamplerAddressingModeClamp = 2, + SamplerAddressingModeRepeat = 3, + SamplerAddressingModeRepeatMirrored = 4, +}; + +enum SamplerFilterMode { + SamplerFilterModeNearest = 0, + SamplerFilterModeLinear = 1, +}; + +enum ImageFormat { + ImageFormatUnknown = 0, + ImageFormatRgba32f = 1, + ImageFormatRgba16f = 2, + ImageFormatR32f = 3, + ImageFormatRgba8 = 4, + ImageFormatRgba8Snorm = 5, + ImageFormatRg32f = 6, + ImageFormatRg16f = 7, + ImageFormatR11fG11fB10f = 8, + ImageFormatR16f = 9, + ImageFormatRgba16 = 10, + ImageFormatRgb10A2 = 11, + ImageFormatRg16 = 12, + ImageFormatRg8 = 13, + ImageFormatR16 = 14, + ImageFormatR8 = 15, + ImageFormatRgba16Snorm = 16, + ImageFormatRg16Snorm = 17, + ImageFormatRg8Snorm = 18, + ImageFormatR16Snorm = 19, + ImageFormatR8Snorm = 20, + ImageFormatRgba32i = 21, + ImageFormatRgba16i = 22, + ImageFormatRgba8i = 23, + ImageFormatR32i = 24, + ImageFormatRg32i = 25, + ImageFormatRg16i = 26, + ImageFormatRg8i = 27, + ImageFormatR16i = 28, + ImageFormatR8i = 29, + ImageFormatRgba32ui = 30, + ImageFormatRgba16ui = 31, + ImageFormatRgba8ui = 32, + ImageFormatR32ui = 33, + ImageFormatRgb10a2ui = 34, + ImageFormatRg32ui = 35, + ImageFormatRg16ui = 36, + ImageFormatRg8ui = 37, + ImageFormatR16ui = 38, + ImageFormatR8ui = 39, +}; + +enum ImageChannelOrder { + ImageChannelOrderR = 0, + ImageChannelOrderA = 1, + ImageChannelOrderRG = 2, + ImageChannelOrderRA = 3, + ImageChannelOrderRGB = 4, + ImageChannelOrderRGBA = 5, + ImageChannelOrderBGRA = 6, + ImageChannelOrderARGB = 7, + ImageChannelOrderIntensity = 8, + ImageChannelOrderLuminance = 9, + ImageChannelOrderRx = 10, + ImageChannelOrderRGx = 11, + ImageChannelOrderRGBx = 12, + ImageChannelOrderDepth = 13, + ImageChannelOrderDepthStencil = 14, + ImageChannelOrdersRGB = 15, + ImageChannelOrdersRGBx = 16, + ImageChannelOrdersRGBA = 17, + ImageChannelOrdersBGRA = 18, + ImageChannelOrderABGR = 19, +}; + +enum ImageChannelDataType { + ImageChannelDataTypeSnormInt8 = 0, + ImageChannelDataTypeSnormInt16 = 1, + ImageChannelDataTypeUnormInt8 = 2, + ImageChannelDataTypeUnormInt16 = 3, + ImageChannelDataTypeUnormShort565 = 4, + ImageChannelDataTypeUnormShort555 = 5, + ImageChannelDataTypeUnormInt101010 = 6, + ImageChannelDataTypeSignedInt8 = 7, + ImageChannelDataTypeSignedInt16 = 8, + ImageChannelDataTypeSignedInt32 = 9, + ImageChannelDataTypeUnsignedInt8 = 10, + ImageChannelDataTypeUnsignedInt16 = 11, + ImageChannelDataTypeUnsignedInt32 = 12, + ImageChannelDataTypeHalfFloat = 13, + ImageChannelDataTypeFloat = 14, + ImageChannelDataTypeUnormInt24 = 15, + ImageChannelDataTypeUnormInt101010_2 = 16, +}; + +enum ImageOperandsShift { + ImageOperandsBiasShift = 0, + ImageOperandsLodShift = 1, + ImageOperandsGradShift = 2, + ImageOperandsConstOffsetShift = 3, + ImageOperandsOffsetShift = 4, + ImageOperandsConstOffsetsShift = 5, + ImageOperandsSampleShift = 6, + ImageOperandsMinLodShift = 7, +}; + +enum ImageOperandsMask { + ImageOperandsMaskNone = 0, + ImageOperandsBiasMask = 0x00000001, + ImageOperandsLodMask = 0x00000002, + ImageOperandsGradMask = 0x00000004, + ImageOperandsConstOffsetMask = 0x00000008, + ImageOperandsOffsetMask = 0x00000010, + ImageOperandsConstOffsetsMask = 0x00000020, + ImageOperandsSampleMask = 0x00000040, + ImageOperandsMinLodMask = 0x00000080, +}; + +enum FPFastMathModeShift { + FPFastMathModeNotNaNShift = 0, + FPFastMathModeNotInfShift = 1, + FPFastMathModeNSZShift = 2, + FPFastMathModeAllowRecipShift = 3, + FPFastMathModeFastShift = 4, +}; + +enum FPFastMathModeMask { + FPFastMathModeMaskNone = 0, + FPFastMathModeNotNaNMask = 0x00000001, + FPFastMathModeNotInfMask = 0x00000002, + FPFastMathModeNSZMask = 0x00000004, + FPFastMathModeAllowRecipMask = 0x00000008, + FPFastMathModeFastMask = 0x00000010, +}; + +enum FPRoundingMode { + FPRoundingModeRTE = 0, + FPRoundingModeRTZ = 1, + FPRoundingModeRTP = 2, + FPRoundingModeRTN = 3, +}; + +enum LinkageType { + LinkageTypeExport = 0, + LinkageTypeImport = 1, +}; + +enum AccessQualifier { + AccessQualifierReadOnly = 0, + AccessQualifierWriteOnly = 1, + AccessQualifierReadWrite = 2, +}; + +enum FunctionParameterAttribute { + FunctionParameterAttributeZext = 0, + FunctionParameterAttributeSext = 1, + FunctionParameterAttributeByVal = 2, + FunctionParameterAttributeSret = 3, + FunctionParameterAttributeNoAlias = 4, + FunctionParameterAttributeNoCapture = 5, + FunctionParameterAttributeNoWrite = 6, + FunctionParameterAttributeNoReadWrite = 7, +}; + +enum Decoration { + DecorationRelaxedPrecision = 0, + DecorationSpecId = 1, + DecorationBlock = 2, + DecorationBufferBlock = 3, + DecorationRowMajor = 4, + DecorationColMajor = 5, + DecorationArrayStride = 6, + DecorationMatrixStride = 7, + DecorationGLSLShared = 8, + DecorationGLSLPacked = 9, + DecorationCPacked = 10, + DecorationBuiltIn = 11, + DecorationNoPerspective = 13, + DecorationFlat = 14, + DecorationPatch = 15, + DecorationCentroid = 16, + DecorationSample = 17, + DecorationInvariant = 18, + DecorationRestrict = 19, + DecorationAliased = 20, + DecorationVolatile = 21, + DecorationConstant = 22, + DecorationCoherent = 23, + DecorationNonWritable = 24, + DecorationNonReadable = 25, + DecorationUniform = 26, + DecorationSaturatedConversion = 28, + DecorationStream = 29, + DecorationLocation = 30, + DecorationComponent = 31, + DecorationIndex = 32, + DecorationBinding = 33, + DecorationDescriptorSet = 34, + DecorationOffset = 35, + DecorationXfbBuffer = 36, + DecorationXfbStride = 37, + DecorationFuncParamAttr = 38, + DecorationFPRoundingMode = 39, + DecorationFPFastMathMode = 40, + DecorationLinkageAttributes = 41, + DecorationNoContraction = 42, + DecorationInputAttachmentIndex = 43, + DecorationAlignment = 44, +}; + +enum BuiltIn { + BuiltInPosition = 0, + BuiltInPointSize = 1, + BuiltInClipDistance = 3, + BuiltInCullDistance = 4, + BuiltInVertexId = 5, + BuiltInInstanceId = 6, + BuiltInPrimitiveId = 7, + BuiltInInvocationId = 8, + BuiltInLayer = 9, + BuiltInViewportIndex = 10, + BuiltInTessLevelOuter = 11, + BuiltInTessLevelInner = 12, + BuiltInTessCoord = 13, + BuiltInPatchVertices = 14, + BuiltInFragCoord = 15, + BuiltInPointCoord = 16, + BuiltInFrontFacing = 17, + BuiltInSampleId = 18, + BuiltInSamplePosition = 19, + BuiltInSampleMask = 20, + BuiltInFragDepth = 22, + BuiltInHelperInvocation = 23, + BuiltInNumWorkgroups = 24, + BuiltInWorkgroupSize = 25, + BuiltInWorkgroupId = 26, + BuiltInLocalInvocationId = 27, + BuiltInGlobalInvocationId = 28, + BuiltInLocalInvocationIndex = 29, + BuiltInWorkDim = 30, + BuiltInGlobalSize = 31, + BuiltInEnqueuedWorkgroupSize = 32, + BuiltInGlobalOffset = 33, + BuiltInGlobalLinearId = 34, + BuiltInSubgroupSize = 36, + BuiltInSubgroupMaxSize = 37, + BuiltInNumSubgroups = 38, + BuiltInNumEnqueuedSubgroups = 39, + BuiltInSubgroupId = 40, + BuiltInSubgroupLocalInvocationId = 41, + BuiltInVertexIndex = 42, + BuiltInInstanceIndex = 43, +}; + +enum SelectionControlShift { + SelectionControlFlattenShift = 0, + SelectionControlDontFlattenShift = 1, +}; + +enum SelectionControlMask { + SelectionControlMaskNone = 0, + SelectionControlFlattenMask = 0x00000001, + SelectionControlDontFlattenMask = 0x00000002, +}; + +enum LoopControlShift { + LoopControlUnrollShift = 0, + LoopControlDontUnrollShift = 1, +}; + +enum LoopControlMask { + LoopControlMaskNone = 0, + LoopControlUnrollMask = 0x00000001, + LoopControlDontUnrollMask = 0x00000002, +}; + +enum FunctionControlShift { + FunctionControlInlineShift = 0, + FunctionControlDontInlineShift = 1, + FunctionControlPureShift = 2, + FunctionControlConstShift = 3, +}; + +enum FunctionControlMask { + FunctionControlMaskNone = 0, + FunctionControlInlineMask = 0x00000001, + FunctionControlDontInlineMask = 0x00000002, + FunctionControlPureMask = 0x00000004, + FunctionControlConstMask = 0x00000008, +}; + +enum MemorySemanticsShift { + MemorySemanticsAcquireShift = 1, + MemorySemanticsReleaseShift = 2, + MemorySemanticsAcquireReleaseShift = 3, + MemorySemanticsSequentiallyConsistentShift = 4, + MemorySemanticsUniformMemoryShift = 6, + MemorySemanticsSubgroupMemoryShift = 7, + MemorySemanticsWorkgroupMemoryShift = 8, + MemorySemanticsCrossWorkgroupMemoryShift = 9, + MemorySemanticsAtomicCounterMemoryShift = 10, + MemorySemanticsImageMemoryShift = 11, +}; + +enum MemorySemanticsMask { + MemorySemanticsMaskNone = 0, + MemorySemanticsAcquireMask = 0x00000002, + MemorySemanticsReleaseMask = 0x00000004, + MemorySemanticsAcquireReleaseMask = 0x00000008, + MemorySemanticsSequentiallyConsistentMask = 0x00000010, + MemorySemanticsUniformMemoryMask = 0x00000040, + MemorySemanticsSubgroupMemoryMask = 0x00000080, + MemorySemanticsWorkgroupMemoryMask = 0x00000100, + MemorySemanticsCrossWorkgroupMemoryMask = 0x00000200, + MemorySemanticsAtomicCounterMemoryMask = 0x00000400, + MemorySemanticsImageMemoryMask = 0x00000800, +}; + +enum MemoryAccessShift { + MemoryAccessVolatileShift = 0, + MemoryAccessAlignedShift = 1, + MemoryAccessNontemporalShift = 2, +}; + +enum MemoryAccessMask { + MemoryAccessMaskNone = 0, + MemoryAccessVolatileMask = 0x00000001, + MemoryAccessAlignedMask = 0x00000002, + MemoryAccessNontemporalMask = 0x00000004, +}; + +enum Scope { + ScopeCrossDevice = 0, + ScopeDevice = 1, + ScopeWorkgroup = 2, + ScopeSubgroup = 3, + ScopeInvocation = 4, +}; + +enum GroupOperation { + GroupOperationReduce = 0, + GroupOperationInclusiveScan = 1, + GroupOperationExclusiveScan = 2, +}; + +enum KernelEnqueueFlags { + KernelEnqueueFlagsNoWait = 0, + KernelEnqueueFlagsWaitKernel = 1, + KernelEnqueueFlagsWaitWorkGroup = 2, +}; + +enum KernelProfilingInfoShift { + KernelProfilingInfoCmdExecTimeShift = 0, +}; + +enum KernelProfilingInfoMask { + KernelProfilingInfoMaskNone = 0, + KernelProfilingInfoCmdExecTimeMask = 0x00000001, +}; + +enum Capability { + CapabilityMatrix = 0, + CapabilityShader = 1, + CapabilityGeometry = 2, + CapabilityTessellation = 3, + CapabilityAddresses = 4, + CapabilityLinkage = 5, + CapabilityKernel = 6, + CapabilityVector16 = 7, + CapabilityFloat16Buffer = 8, + CapabilityFloat16 = 9, + CapabilityFloat64 = 10, + CapabilityInt64 = 11, + CapabilityInt64Atomics = 12, + CapabilityImageBasic = 13, + CapabilityImageReadWrite = 14, + CapabilityImageMipmap = 15, + CapabilityPipes = 17, + CapabilityGroups = 18, + CapabilityDeviceEnqueue = 19, + CapabilityLiteralSampler = 20, + CapabilityAtomicStorage = 21, + CapabilityInt16 = 22, + CapabilityTessellationPointSize = 23, + CapabilityGeometryPointSize = 24, + CapabilityImageGatherExtended = 25, + CapabilityStorageImageMultisample = 27, + CapabilityUniformBufferArrayDynamicIndexing = 28, + CapabilitySampledImageArrayDynamicIndexing = 29, + CapabilityStorageBufferArrayDynamicIndexing = 30, + CapabilityStorageImageArrayDynamicIndexing = 31, + CapabilityClipDistance = 32, + CapabilityCullDistance = 33, + CapabilityImageCubeArray = 34, + CapabilitySampleRateShading = 35, + CapabilityImageRect = 36, + CapabilitySampledRect = 37, + CapabilityGenericPointer = 38, + CapabilityInt8 = 39, + CapabilityInputAttachment = 40, + CapabilitySparseResidency = 41, + CapabilityMinLod = 42, + CapabilitySampled1D = 43, + CapabilityImage1D = 44, + CapabilitySampledCubeArray = 45, + CapabilitySampledBuffer = 46, + CapabilityImageBuffer = 47, + CapabilityImageMSArray = 48, + CapabilityStorageImageExtendedFormats = 49, + CapabilityImageQuery = 50, + CapabilityDerivativeControl = 51, + CapabilityInterpolationFunction = 52, + CapabilityTransformFeedback = 53, + CapabilityGeometryStreams = 54, + CapabilityStorageImageReadWithoutFormat = 55, + CapabilityStorageImageWriteWithoutFormat = 56, + CapabilityMultiViewport = 57, +}; + +enum Op { + OpNop = 0, + OpUndef = 1, + OpSourceContinued = 2, + OpSource = 3, + OpSourceExtension = 4, + OpName = 5, + OpMemberName = 6, + OpString = 7, + OpLine = 8, + OpExtension = 10, + OpExtInstImport = 11, + OpExtInst = 12, + OpMemoryModel = 14, + OpEntryPoint = 15, + OpExecutionMode = 16, + OpCapability = 17, + OpTypeVoid = 19, + OpTypeBool = 20, + OpTypeInt = 21, + OpTypeFloat = 22, + OpTypeVector = 23, + OpTypeMatrix = 24, + OpTypeImage = 25, + OpTypeSampler = 26, + OpTypeSampledImage = 27, + OpTypeArray = 28, + OpTypeRuntimeArray = 29, + OpTypeStruct = 30, + OpTypeOpaque = 31, + OpTypePointer = 32, + OpTypeFunction = 33, + OpTypeEvent = 34, + OpTypeDeviceEvent = 35, + OpTypeReserveId = 36, + OpTypeQueue = 37, + OpTypePipe = 38, + OpTypeForwardPointer = 39, + OpConstantTrue = 41, + OpConstantFalse = 42, + OpConstant = 43, + OpConstantComposite = 44, + OpConstantSampler = 45, + OpConstantNull = 46, + OpSpecConstantTrue = 48, + OpSpecConstantFalse = 49, + OpSpecConstant = 50, + OpSpecConstantComposite = 51, + OpSpecConstantOp = 52, + OpFunction = 54, + OpFunctionParameter = 55, + OpFunctionEnd = 56, + OpFunctionCall = 57, + OpVariable = 59, + OpImageTexelPointer = 60, + OpLoad = 61, + OpStore = 62, + OpCopyMemory = 63, + OpCopyMemorySized = 64, + OpAccessChain = 65, + OpInBoundsAccessChain = 66, + OpPtrAccessChain = 67, + OpArrayLength = 68, + OpGenericPtrMemSemantics = 69, + OpInBoundsPtrAccessChain = 70, + OpDecorate = 71, + OpMemberDecorate = 72, + OpDecorationGroup = 73, + OpGroupDecorate = 74, + OpGroupMemberDecorate = 75, + OpVectorExtractDynamic = 77, + OpVectorInsertDynamic = 78, + OpVectorShuffle = 79, + OpCompositeConstruct = 80, + OpCompositeExtract = 81, + OpCompositeInsert = 82, + OpCopyObject = 83, + OpTranspose = 84, + OpSampledImage = 86, + OpImageSampleImplicitLod = 87, + OpImageSampleExplicitLod = 88, + OpImageSampleDrefImplicitLod = 89, + OpImageSampleDrefExplicitLod = 90, + OpImageSampleProjImplicitLod = 91, + OpImageSampleProjExplicitLod = 92, + OpImageSampleProjDrefImplicitLod = 93, + OpImageSampleProjDrefExplicitLod = 94, + OpImageFetch = 95, + OpImageGather = 96, + OpImageDrefGather = 97, + OpImageRead = 98, + OpImageWrite = 99, + OpImage = 100, + OpImageQueryFormat = 101, + OpImageQueryOrder = 102, + OpImageQuerySizeLod = 103, + OpImageQuerySize = 104, + OpImageQueryLod = 105, + OpImageQueryLevels = 106, + OpImageQuerySamples = 107, + OpConvertFToU = 109, + OpConvertFToS = 110, + OpConvertSToF = 111, + OpConvertUToF = 112, + OpUConvert = 113, + OpSConvert = 114, + OpFConvert = 115, + OpQuantizeToF16 = 116, + OpConvertPtrToU = 117, + OpSatConvertSToU = 118, + OpSatConvertUToS = 119, + OpConvertUToPtr = 120, + OpPtrCastToGeneric = 121, + OpGenericCastToPtr = 122, + OpGenericCastToPtrExplicit = 123, + OpBitcast = 124, + OpSNegate = 126, + OpFNegate = 127, + OpIAdd = 128, + OpFAdd = 129, + OpISub = 130, + OpFSub = 131, + OpIMul = 132, + OpFMul = 133, + OpUDiv = 134, + OpSDiv = 135, + OpFDiv = 136, + OpUMod = 137, + OpSRem = 138, + OpSMod = 139, + OpFRem = 140, + OpFMod = 141, + OpVectorTimesScalar = 142, + OpMatrixTimesScalar = 143, + OpVectorTimesMatrix = 144, + OpMatrixTimesVector = 145, + OpMatrixTimesMatrix = 146, + OpOuterProduct = 147, + OpDot = 148, + OpIAddCarry = 149, + OpISubBorrow = 150, + OpUMulExtended = 151, + OpSMulExtended = 152, + OpAny = 154, + OpAll = 155, + OpIsNan = 156, + OpIsInf = 157, + OpIsFinite = 158, + OpIsNormal = 159, + OpSignBitSet = 160, + OpLessOrGreater = 161, + OpOrdered = 162, + OpUnordered = 163, + OpLogicalEqual = 164, + OpLogicalNotEqual = 165, + OpLogicalOr = 166, + OpLogicalAnd = 167, + OpLogicalNot = 168, + OpSelect = 169, + OpIEqual = 170, + OpINotEqual = 171, + OpUGreaterThan = 172, + OpSGreaterThan = 173, + OpUGreaterThanEqual = 174, + OpSGreaterThanEqual = 175, + OpULessThan = 176, + OpSLessThan = 177, + OpULessThanEqual = 178, + OpSLessThanEqual = 179, + OpFOrdEqual = 180, + OpFUnordEqual = 181, + OpFOrdNotEqual = 182, + OpFUnordNotEqual = 183, + OpFOrdLessThan = 184, + OpFUnordLessThan = 185, + OpFOrdGreaterThan = 186, + OpFUnordGreaterThan = 187, + OpFOrdLessThanEqual = 188, + OpFUnordLessThanEqual = 189, + OpFOrdGreaterThanEqual = 190, + OpFUnordGreaterThanEqual = 191, + OpShiftRightLogical = 194, + OpShiftRightArithmetic = 195, + OpShiftLeftLogical = 196, + OpBitwiseOr = 197, + OpBitwiseXor = 198, + OpBitwiseAnd = 199, + OpNot = 200, + OpBitFieldInsert = 201, + OpBitFieldSExtract = 202, + OpBitFieldUExtract = 203, + OpBitReverse = 204, + OpBitCount = 205, + OpDPdx = 207, + OpDPdy = 208, + OpFwidth = 209, + OpDPdxFine = 210, + OpDPdyFine = 211, + OpFwidthFine = 212, + OpDPdxCoarse = 213, + OpDPdyCoarse = 214, + OpFwidthCoarse = 215, + OpEmitVertex = 218, + OpEndPrimitive = 219, + OpEmitStreamVertex = 220, + OpEndStreamPrimitive = 221, + OpControlBarrier = 224, + OpMemoryBarrier = 225, + OpAtomicLoad = 227, + OpAtomicStore = 228, + OpAtomicExchange = 229, + OpAtomicCompareExchange = 230, + OpAtomicCompareExchangeWeak = 231, + OpAtomicIIncrement = 232, + OpAtomicIDecrement = 233, + OpAtomicIAdd = 234, + OpAtomicISub = 235, + OpAtomicSMin = 236, + OpAtomicUMin = 237, + OpAtomicSMax = 238, + OpAtomicUMax = 239, + OpAtomicAnd = 240, + OpAtomicOr = 241, + OpAtomicXor = 242, + OpPhi = 245, + OpLoopMerge = 246, + OpSelectionMerge = 247, + OpLabel = 248, + OpBranch = 249, + OpBranchConditional = 250, + OpSwitch = 251, + OpKill = 252, + OpReturn = 253, + OpReturnValue = 254, + OpUnreachable = 255, + OpLifetimeStart = 256, + OpLifetimeStop = 257, + OpGroupAsyncCopy = 259, + OpGroupWaitEvents = 260, + OpGroupAll = 261, + OpGroupAny = 262, + OpGroupBroadcast = 263, + OpGroupIAdd = 264, + OpGroupFAdd = 265, + OpGroupFMin = 266, + OpGroupUMin = 267, + OpGroupSMin = 268, + OpGroupFMax = 269, + OpGroupUMax = 270, + OpGroupSMax = 271, + OpReadPipe = 274, + OpWritePipe = 275, + OpReservedReadPipe = 276, + OpReservedWritePipe = 277, + OpReserveReadPipePackets = 278, + OpReserveWritePipePackets = 279, + OpCommitReadPipe = 280, + OpCommitWritePipe = 281, + OpIsValidReserveId = 282, + OpGetNumPipePackets = 283, + OpGetMaxPipePackets = 284, + OpGroupReserveReadPipePackets = 285, + OpGroupReserveWritePipePackets = 286, + OpGroupCommitReadPipe = 287, + OpGroupCommitWritePipe = 288, + OpEnqueueMarker = 291, + OpEnqueueKernel = 292, + OpGetKernelNDrangeSubGroupCount = 293, + OpGetKernelNDrangeMaxSubGroupSize = 294, + OpGetKernelWorkGroupSize = 295, + OpGetKernelPreferredWorkGroupSizeMultiple = 296, + OpRetainEvent = 297, + OpReleaseEvent = 298, + OpCreateUserEvent = 299, + OpIsValidEvent = 300, + OpSetUserEventStatus = 301, + OpCaptureEventProfilingInfo = 302, + OpGetDefaultQueue = 303, + OpBuildNDRange = 304, + OpImageSparseSampleImplicitLod = 305, + OpImageSparseSampleExplicitLod = 306, + OpImageSparseSampleDrefImplicitLod = 307, + OpImageSparseSampleDrefExplicitLod = 308, + OpImageSparseSampleProjImplicitLod = 309, + OpImageSparseSampleProjExplicitLod = 310, + OpImageSparseSampleProjDrefImplicitLod = 311, + OpImageSparseSampleProjDrefExplicitLod = 312, + OpImageSparseFetch = 313, + OpImageSparseGather = 314, + OpImageSparseDrefGather = 315, + OpImageSparseTexelsResident = 316, + OpNoLine = 317, + OpAtomicFlagTestAndSet = 318, + OpAtomicFlagClear = 319, + OpImageSparseRead = 320, +}; + +// Overload operator| for mask bit combining + +inline ImageOperandsMask operator|(ImageOperandsMask a, ImageOperandsMask b) { return ImageOperandsMask(unsigned(a) | unsigned(b)); } +inline FPFastMathModeMask operator|(FPFastMathModeMask a, FPFastMathModeMask b) { return FPFastMathModeMask(unsigned(a) | unsigned(b)); } +inline SelectionControlMask operator|(SelectionControlMask a, SelectionControlMask b) { return SelectionControlMask(unsigned(a) | unsigned(b)); } +inline LoopControlMask operator|(LoopControlMask a, LoopControlMask b) { return LoopControlMask(unsigned(a) | unsigned(b)); } +inline FunctionControlMask operator|(FunctionControlMask a, FunctionControlMask b) { return FunctionControlMask(unsigned(a) | unsigned(b)); } +inline MemorySemanticsMask operator|(MemorySemanticsMask a, MemorySemanticsMask b) { return MemorySemanticsMask(unsigned(a) | unsigned(b)); } +inline MemoryAccessMask operator|(MemoryAccessMask a, MemoryAccessMask b) { return MemoryAccessMask(unsigned(a) | unsigned(b)); } +inline KernelProfilingInfoMask operator|(KernelProfilingInfoMask a, KernelProfilingInfoMask b) { return KernelProfilingInfoMask(unsigned(a) | unsigned(b)); } + +} // end namespace spv + +#endif // #ifndef spirv_HPP + diff --git a/libs/openFrameworks/vk/spirv-cross/include/spirv_cfg.cpp b/libs/openFrameworks/vk/spirv-cross/include/spirv_cfg.cpp new file mode 100644 index 00000000000..32549edc726 --- /dev/null +++ b/libs/openFrameworks/vk/spirv-cross/include/spirv_cfg.cpp @@ -0,0 +1,230 @@ +/* + * Copyright 2016-2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "spirv_cfg.hpp" +#include "spirv_cross.hpp" +#include +#include + +using namespace std; + +namespace spirv_cross +{ +CFG::CFG(Compiler &compiler_, const SPIRFunction &func_) + : compiler(compiler_) + , func(func_) +{ + preceding_edges.resize(compiler.get_current_id_bound()); + succeeding_edges.resize(compiler.get_current_id_bound()); + visit_order.resize(compiler.get_current_id_bound()); + immediate_dominators.resize(compiler.get_current_id_bound()); + + build_post_order_visit_order(); + build_immediate_dominators(); +} + +uint32_t CFG::find_common_dominator(uint32_t a, uint32_t b) const +{ + while (a != b) + { + if (visit_order[a] < visit_order[b]) + a = immediate_dominators[a]; + else + b = immediate_dominators[b]; + } + return a; +} + +uint32_t CFG::update_common_dominator(uint32_t a, uint32_t b) +{ + auto dominator = find_common_dominator(immediate_dominators[a], immediate_dominators[b]); + immediate_dominators[a] = dominator; + immediate_dominators[b] = dominator; + return dominator; +} + +void CFG::build_immediate_dominators() +{ + // Traverse the post-order in reverse and build up the immediate dominator tree. + fill(begin(immediate_dominators), end(immediate_dominators), 0); + immediate_dominators[func.entry_block] = func.entry_block; + + for (auto i = post_order.size(); i; i--) + { + uint32_t block = post_order[i - 1]; + auto &pred = preceding_edges[block]; + if (pred.empty()) // This is for the entry block, but we've already set up the dominators. + continue; + + for (auto &edge : pred) + { + if (immediate_dominators[block]) + { + assert(immediate_dominators[edge]); + immediate_dominators[block] = update_common_dominator(block, edge); + } + else + immediate_dominators[block] = edge; + } + } +} + +bool CFG::is_back_edge(uint32_t to) const +{ + // We have a back edge if the visit order is set with the temporary magic value 0. + // Crossing edges will have already been recorded with a visit order. + return visit_order[to] == 0; +} + +bool CFG::post_order_visit(uint32_t block_id) +{ + // If we have already branched to this block (back edge), stop recursion. + // If our branches are back-edges, we do not record them. + // We have to record crossing edges however. + if (visit_order[block_id] >= 0) + return !is_back_edge(block_id); + + // Block back-edges from recursively revisiting ourselves. + visit_order[block_id] = 0; + + // First visit our branch targets. + auto &block = compiler.get(block_id); + switch (block.terminator) + { + case SPIRBlock::Direct: + if (post_order_visit(block.next_block)) + add_branch(block_id, block.next_block); + break; + + case SPIRBlock::Select: + if (post_order_visit(block.true_block)) + add_branch(block_id, block.true_block); + if (post_order_visit(block.false_block)) + add_branch(block_id, block.false_block); + break; + + case SPIRBlock::MultiSelect: + for (auto &target : block.cases) + { + if (post_order_visit(target.block)) + add_branch(block_id, target.block); + } + if (block.default_block && post_order_visit(block.default_block)) + add_branch(block_id, block.default_block); + break; + + default: + break; + } + + // Then visit ourselves. Start counting at one, to let 0 be a magic value for testing back vs. crossing edges. + visit_order[block_id] = ++visit_count; + post_order.push_back(block_id); + return true; +} + +void CFG::build_post_order_visit_order() +{ + uint32_t block = func.entry_block; + visit_count = 0; + fill(begin(visit_order), end(visit_order), -1); + post_order.clear(); + post_order_visit(block); +} + +void CFG::add_branch(uint32_t from, uint32_t to) +{ + const auto add_unique = [](vector &l, uint32_t value) { + auto itr = find(begin(l), end(l), value); + if (itr == end(l)) + l.push_back(value); + }; + add_unique(preceding_edges[to], from); + add_unique(succeeding_edges[from], to); +} + +DominatorBuilder::DominatorBuilder(const CFG &cfg_) + : cfg(cfg_) +{ +} + +void DominatorBuilder::add_block(uint32_t block) +{ + if (!cfg.get_immediate_dominator(block)) + { + // Unreachable block via the CFG, we will never emit this code anyways. + return; + } + + if (!dominator) + { + dominator = block; + return; + } + + if (block != dominator) + dominator = cfg.find_common_dominator(block, dominator); +} + +void DominatorBuilder::lift_continue_block_dominator() +{ + // It is possible for a continue block to be the dominator if a variable is only accessed inside the while block of a do-while loop. + // We cannot safely declare variables inside a continue block, so move any variable declared + // in a continue block to the entry block to simplify. + // It makes very little sense for a continue block to ever be a dominator, so fall back to the simplest + // solution. + + if (!dominator) + return; + + auto &block = cfg.get_compiler().get(dominator); + auto post_order = cfg.get_visit_order(dominator); + + // If we are branching to a block with a higher post-order traversal index (continue blocks), we have a problem + // since we cannot create sensible GLSL code for this, fallback to entry block. + bool back_edge_dominator = false; + switch (block.terminator) + { + case SPIRBlock::Direct: + if (cfg.get_visit_order(block.next_block) > post_order) + back_edge_dominator = true; + break; + + case SPIRBlock::Select: + if (cfg.get_visit_order(block.true_block) > post_order) + back_edge_dominator = true; + if (cfg.get_visit_order(block.false_block) > post_order) + back_edge_dominator = true; + break; + + case SPIRBlock::MultiSelect: + for (auto &target : block.cases) + { + if (cfg.get_visit_order(target.block) > post_order) + back_edge_dominator = true; + } + if (block.default_block && cfg.get_visit_order(block.default_block) > post_order) + back_edge_dominator = true; + break; + + default: + break; + } + + if (back_edge_dominator) + dominator = cfg.get_function().entry_block; +} +} diff --git a/libs/openFrameworks/vk/spirv-cross/include/spirv_cfg.hpp b/libs/openFrameworks/vk/spirv-cross/include/spirv_cfg.hpp new file mode 100644 index 00000000000..7e214404523 --- /dev/null +++ b/libs/openFrameworks/vk/spirv-cross/include/spirv_cfg.hpp @@ -0,0 +1,116 @@ +/* + * Copyright 2016-2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SPIRV_CROSS_CFG_HPP +#define SPIRV_CROSS_CFG_HPP + +#include "spirv_common.hpp" +#include + +namespace spirv_cross +{ +class Compiler; +class CFG +{ +public: + CFG(Compiler &compiler, const SPIRFunction &function); + + Compiler &get_compiler() + { + return compiler; + } + + const Compiler &get_compiler() const + { + return compiler; + } + + const SPIRFunction &get_function() const + { + return func; + } + + uint32_t get_immediate_dominator(uint32_t block) const + { + return immediate_dominators[block]; + } + + uint32_t get_visit_order(uint32_t block) const + { + int v = visit_order[block]; + assert(v > 0); + return uint32_t(v); + } + + uint32_t find_common_dominator(uint32_t a, uint32_t b) const; + + const std::vector &get_preceding_edges(uint32_t block) const + { + return preceding_edges[block]; + } + + const std::vector &get_succeeding_edges(uint32_t block) const + { + return succeeding_edges[block]; + } + + template + void walk_from(uint32_t block, const Op &op) const + { + op(block); + for (auto b : succeeding_edges[block]) + walk_from(b, op); + } + +private: + Compiler &compiler; + const SPIRFunction &func; + std::vector> preceding_edges; + std::vector> succeeding_edges; + std::vector immediate_dominators; + std::vector visit_order; + std::vector post_order; + + void add_branch(uint32_t from, uint32_t to); + void build_post_order_visit_order(); + void build_immediate_dominators(); + bool post_order_visit(uint32_t block); + uint32_t visit_count = 0; + + uint32_t update_common_dominator(uint32_t a, uint32_t b); + bool is_back_edge(uint32_t to) const; +}; + +class DominatorBuilder +{ +public: + DominatorBuilder(const CFG &cfg); + + void add_block(uint32_t block); + uint32_t get_dominator() const + { + return dominator; + } + + void lift_continue_block_dominator(); + +private: + const CFG &cfg; + uint32_t dominator = 0; +}; +} + +#endif diff --git a/libs/openFrameworks/vk/spirv-cross/include/spirv_common.hpp b/libs/openFrameworks/vk/spirv-cross/include/spirv_common.hpp new file mode 100644 index 00000000000..79e047029c9 --- /dev/null +++ b/libs/openFrameworks/vk/spirv-cross/include/spirv_common.hpp @@ -0,0 +1,974 @@ +/* + * Copyright 2015-2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SPIRV_CROSS_COMMON_HPP +#define SPIRV_CROSS_COMMON_HPP + +#include "spirv.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace spirv_cross +{ + +#ifdef SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS +#ifndef _MSC_VER +[[noreturn]] +#endif + inline void + report_and_abort(const std::string &msg) +{ +#ifdef NDEBUG + (void)msg; +#else + fprintf(stderr, "There was a compiler error: %s\n", msg.c_str()); +#endif + abort(); +} + +#define SPIRV_CROSS_THROW(x) report_and_abort(x) +#else +class CompilerError : public std::runtime_error +{ +public: + CompilerError(const std::string &str) + : std::runtime_error(str) + { + } +}; + +#define SPIRV_CROSS_THROW(x) throw CompilerError(x) +#endif + +#if __cplusplus >= 201402l +#define SPIRV_CROSS_DEPRECATED(reason) [[deprecated(reason)]] +#elif defined(__GNUC__) +#define SPIRV_CROSS_DEPRECATED(reason) __attribute__((deprecated)) +#elif defined(_MSC_VER) +#define SPIRV_CROSS_DEPRECATED(reason) __declspec(deprecated(reason)) +#else +#define SPIRV_CROSS_DEPRECATED(reason) +#endif + +namespace inner +{ +template +void join_helper(std::ostringstream &stream, T &&t) +{ + stream << std::forward(t); +} + +template +void join_helper(std::ostringstream &stream, T &&t, Ts &&... ts) +{ + stream << std::forward(t); + join_helper(stream, std::forward(ts)...); +} +} + +// Helper template to avoid lots of nasty string temporary munging. +template +std::string join(Ts &&... ts) +{ + std::ostringstream stream; + inner::join_helper(stream, std::forward(ts)...); + return stream.str(); +} + +inline std::string merge(const std::vector &list) +{ + std::string s; + for (auto &elem : list) + { + s += elem; + if (&elem != &list.back()) + s += ", "; + } + return s; +} + +template +inline std::string convert_to_string(T &&t) +{ + return std::to_string(std::forward(t)); +} + +// Allow implementations to set a convenient standard precision +#ifndef SPIRV_CROSS_FLT_FMT +#define SPIRV_CROSS_FLT_FMT "%.32g" +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4996) +#endif + +inline std::string convert_to_string(float t) +{ + // std::to_string for floating point values is broken. + // Fallback to something more sane. + char buf[64]; + sprintf(buf, SPIRV_CROSS_FLT_FMT, t); + // Ensure that the literal is float. + if (!strchr(buf, '.') && !strchr(buf, 'e')) + strcat(buf, ".0"); + return buf; +} + +inline std::string convert_to_string(double t) +{ + // std::to_string for floating point values is broken. + // Fallback to something more sane. + char buf[64]; + sprintf(buf, SPIRV_CROSS_FLT_FMT, t); + // Ensure that the literal is float. + if (!strchr(buf, '.') && !strchr(buf, 'e')) + strcat(buf, ".0"); + return buf; +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +struct Instruction +{ + Instruction(const std::vector &spirv, uint32_t &index); + + uint16_t op; + uint16_t count; + uint32_t offset; + uint32_t length; +}; + +// Helper for Variant interface. +struct IVariant +{ + virtual ~IVariant() = default; + uint32_t self = 0; +}; + +enum Types +{ + TypeNone, + TypeType, + TypeVariable, + TypeConstant, + TypeFunction, + TypeFunctionPrototype, + TypePointer, + TypeBlock, + TypeExtension, + TypeExpression, + TypeConstantOp, + TypeUndef +}; + +struct SPIRUndef : IVariant +{ + enum + { + type = TypeUndef + }; + SPIRUndef(uint32_t basetype_) + : basetype(basetype_) + { + } + uint32_t basetype; +}; + +struct SPIRConstantOp : IVariant +{ + enum + { + type = TypeConstantOp + }; + + SPIRConstantOp(uint32_t result_type, spv::Op op, const uint32_t *args, uint32_t length) + : opcode(op) + , arguments(args, args + length) + , basetype(result_type) + { + } + + spv::Op opcode; + std::vector arguments; + uint32_t basetype; +}; + +struct SPIRType : IVariant +{ + enum + { + type = TypeType + }; + + enum BaseType + { + Unknown, + Void, + Boolean, + Char, + Int, + UInt, + Int64, + UInt64, + AtomicCounter, + Float, + Double, + Struct, + Image, + SampledImage, + Sampler + }; + + // Scalar/vector/matrix support. + BaseType basetype = Unknown; + uint32_t width = 0; + uint32_t vecsize = 1; + uint32_t columns = 1; + + // Arrays, support array of arrays by having a vector of array sizes. + std::vector array; + + // Array elements can be either specialization constants or specialization ops. + // This array determines how to interpret the array size. + // If an element is true, the element is a literal, + // otherwise, it's an expression, which must be resolved on demand. + // The actual size is not really known until runtime. + std::vector array_size_literal; + + // Pointers + bool pointer = false; + spv::StorageClass storage = spv::StorageClassGeneric; + + std::vector member_types; + + struct Image + { + uint32_t type; + spv::Dim dim; + bool depth; + bool arrayed; + bool ms; + uint32_t sampled; + spv::ImageFormat format; + } image; + + // Structs can be declared multiple times if they are used as part of interface blocks. + // We want to detect this so that we only emit the struct definition once. + // Since we cannot rely on OpName to be equal, we need to figure out aliases. + uint32_t type_alias = 0; + + // Denotes the type which this type is based on. + // Allows the backend to traverse how a complex type is built up during access chains. + uint32_t parent_type = 0; + + // Used in backends to avoid emitting members with conflicting names. + std::unordered_set member_name_cache; +}; + +struct SPIRExtension : IVariant +{ + enum + { + type = TypeExtension + }; + + enum Extension + { + Unsupported, + GLSL + }; + + SPIRExtension(Extension ext_) + : ext(ext_) + { + } + + Extension ext; +}; + +// SPIREntryPoint is not a variant since its IDs are used to decorate OpFunction, +// so in order to avoid conflicts, we can't stick them in the ids array. +struct SPIREntryPoint +{ + SPIREntryPoint(uint32_t self_, spv::ExecutionModel execution_model, std::string entry_name) + : self(self_) + , name(std::move(entry_name)) + , model(execution_model) + { + } + SPIREntryPoint() = default; + + uint32_t self = 0; + std::string name; + std::vector interface_variables; + + uint64_t flags = 0; + struct + { + uint32_t x = 0, y = 0, z = 0; + } workgroup_size; + uint32_t invocations = 0; + uint32_t output_vertices = 0; + spv::ExecutionModel model; +}; + +struct SPIRExpression : IVariant +{ + enum + { + type = TypeExpression + }; + + // Only created by the backend target to avoid creating tons of temporaries. + SPIRExpression(std::string expr, uint32_t expression_type_, bool immutable_) + : expression(move(expr)) + , expression_type(expression_type_) + , immutable(immutable_) + { + } + + // If non-zero, prepend expression with to_expression(base_expression). + // Used in amortizing multiple calls to to_expression() + // where in certain cases that would quickly force a temporary when not needed. + uint32_t base_expression = 0; + + std::string expression; + uint32_t expression_type = 0; + + // If this expression is a forwarded load, + // allow us to reference the original variable. + uint32_t loaded_from = 0; + + // If this expression will never change, we can avoid lots of temporaries + // in high level source. + // An expression being immutable can be speculative, + // it is assumed that this is true almost always. + bool immutable = false; + + // If this expression has been used while invalidated. + bool used_while_invalidated = false; + + // Before use, this expression must be transposed. + // This is needed for targets which don't support row_major layouts. + bool need_transpose = false; + + // A list of expressions which this expression depends on. + std::vector expression_dependencies; +}; + +struct SPIRFunctionPrototype : IVariant +{ + enum + { + type = TypeFunctionPrototype + }; + + SPIRFunctionPrototype(uint32_t return_type_) + : return_type(return_type_) + { + } + + uint32_t return_type; + std::vector parameter_types; +}; + +struct SPIRBlock : IVariant +{ + enum + { + type = TypeBlock + }; + + enum Terminator + { + Unknown, + Direct, // Emit next block directly without a particular condition. + + Select, // Block ends with an if/else block. + MultiSelect, // Block ends with switch statement. + + Return, // Block ends with return. + Unreachable, // Noop + Kill // Discard + }; + + enum Merge + { + MergeNone, + MergeLoop, + MergeSelection + }; + + enum Method + { + MergeToSelectForLoop, + MergeToDirectForLoop + }; + + enum ContinueBlockType + { + ContinueNone, + + // Continue block is branchless and has at least one instruction. + ForLoop, + + // Noop continue block. + WhileLoop, + + // Continue block is conditional. + DoWhileLoop, + + // Highly unlikely that anything will use this, + // since it is really awkward/impossible to express in GLSL. + ComplexLoop + }; + + enum + { + NoDominator = 0xffffffffu + }; + + Terminator terminator = Unknown; + Merge merge = MergeNone; + uint32_t next_block = 0; + uint32_t merge_block = 0; + uint32_t continue_block = 0; + + uint32_t return_value = 0; // If 0, return nothing (void). + uint32_t condition = 0; + uint32_t true_block = 0; + uint32_t false_block = 0; + uint32_t default_block = 0; + + std::vector ops; + + struct Phi + { + uint32_t local_variable; // flush local variable ... + uint32_t parent; // If we're in from_block and want to branch into this block ... + uint32_t function_variable; // to this function-global "phi" variable first. + }; + + // Before entering this block flush out local variables to magical "phi" variables. + std::vector phi_variables; + + // Declare these temporaries before beginning the block. + // Used for handling complex continue blocks which have side effects. + std::vector> declare_temporary; + + struct Case + { + uint32_t value; + uint32_t block; + }; + std::vector cases; + + // If we have tried to optimize code for this block but failed, + // keep track of this. + bool disable_block_optimization = false; + + // If the continue block is complex, fallback to "dumb" for loops. + bool complex_continue = false; + + // The dominating block which this block might be within. + // Used in continue; blocks to determine if we really need to write continue. + uint32_t loop_dominator = 0; + + // All access to these variables are dominated by this block, + // so before branching anywhere we need to make sure that we declare these variables. + std::vector dominated_variables; + + // These are variables which should be declared in a for loop header, if we + // fail to use a classic for-loop, + // we remove these variables, and fall back to regular variables outside the loop. + std::vector loop_variables; +}; + +struct SPIRFunction : IVariant +{ + enum + { + type = TypeFunction + }; + + SPIRFunction(uint32_t return_type_, uint32_t function_type_) + : return_type(return_type_) + , function_type(function_type_) + { + } + + struct Parameter + { + uint32_t type; + uint32_t id; + uint32_t read_count; + uint32_t write_count; + + // Set to true if this parameter aliases a global variable, + // used mostly in Metal where global variables + // have to be passed down to functions as regular arguments. + // However, for this kind of variable, we should not care about + // read and write counts as access to the function arguments + // is not local to the function in question. + bool alias_global_variable; + }; + + // When calling a function, and we're remapping separate image samplers, + // resolve these arguments into combined image samplers and pass them + // as additional arguments in this order. + // It gets more complicated as functions can pull in their own globals + // and combine them with parameters, + // so we need to distinguish if something is local parameter index + // or a global ID. + struct CombinedImageSamplerParameter + { + uint32_t id; + uint32_t image_id; + uint32_t sampler_id; + bool global_image; + bool global_sampler; + }; + + uint32_t return_type; + uint32_t function_type; + std::vector arguments; + + // Can be used by backends to add magic arguments. + // Currently used by combined image/sampler implementation. + + std::vector shadow_arguments; + std::vector local_variables; + uint32_t entry_block = 0; + std::vector blocks; + std::vector combined_parameters; + + void add_local_variable(uint32_t id) + { + local_variables.push_back(id); + } + + void add_parameter(uint32_t parameter_type, uint32_t id, bool alias_global_variable = false) + { + // Arguments are read-only until proven otherwise. + arguments.push_back({ parameter_type, id, 0u, 0u, alias_global_variable }); + } + + bool active = false; + bool flush_undeclared = true; + bool do_combined_parameters = true; + bool analyzed_variable_scope = false; +}; + +struct SPIRVariable : IVariant +{ + enum + { + type = TypeVariable + }; + + SPIRVariable() = default; + SPIRVariable(uint32_t basetype_, spv::StorageClass storage_, uint32_t initializer_ = 0) + : basetype(basetype_) + , storage(storage_) + , initializer(initializer_) + { + } + + uint32_t basetype = 0; + spv::StorageClass storage = spv::StorageClassGeneric; + uint32_t decoration = 0; + uint32_t initializer = 0; + + std::vector dereference_chain; + bool compat_builtin = false; + + // If a variable is shadowed, we only statically assign to it + // and never actually emit a statement for it. + // When we read the variable as an expression, just forward + // shadowed_id as the expression. + bool statically_assigned = false; + uint32_t static_expression = 0; + + // Temporaries which can remain forwarded as long as this variable is not modified. + std::vector dependees; + bool forwardable = true; + + bool deferred_declaration = false; + bool phi_variable = false; + bool remapped_variable = false; + uint32_t remapped_components = 0; + + // The block which dominates all access to this variable. + uint32_t dominator = 0; + // If true, this variable is a loop variable, when accessing the variable + // outside a loop, + // we should statically forward it. + bool loop_variable = false; + // Set to true while we're inside the for loop. + bool loop_variable_enable = false; + + SPIRFunction::Parameter *parameter = nullptr; +}; + +struct SPIRConstant : IVariant +{ + enum + { + type = TypeConstant + }; + + union Constant { + uint32_t u32; + int32_t i32; + float f32; + + uint64_t u64; + int64_t i64; + double f64; + }; + + struct ConstantVector + { + Constant r[4]; + uint32_t vecsize; + }; + + struct ConstantMatrix + { + ConstantVector c[4]; + uint32_t columns; + }; + + inline uint32_t scalar(uint32_t col = 0, uint32_t row = 0) const + { + return m.c[col].r[row].u32; + } + + inline float scalar_f32(uint32_t col = 0, uint32_t row = 0) const + { + return m.c[col].r[row].f32; + } + + inline int32_t scalar_i32(uint32_t col = 0, uint32_t row = 0) const + { + return m.c[col].r[row].i32; + } + + inline double scalar_f64(uint32_t col = 0, uint32_t row = 0) const + { + return m.c[col].r[row].f64; + } + + inline int64_t scalar_i64(uint32_t col = 0, uint32_t row = 0) const + { + return m.c[col].r[row].i64; + } + + inline uint64_t scalar_u64(uint32_t col = 0, uint32_t row = 0) const + { + return m.c[col].r[row].u64; + } + + inline const ConstantVector &vector() const + { + return m.c[0]; + } + inline uint32_t vector_size() const + { + return m.c[0].vecsize; + } + inline uint32_t columns() const + { + return m.columns; + } + + SPIRConstant(uint32_t constant_type_, const uint32_t *elements, uint32_t num_elements) + : constant_type(constant_type_) + { + subconstants.insert(end(subconstants), elements, elements + num_elements); + } + + SPIRConstant(uint32_t constant_type_, uint32_t v0) + : constant_type(constant_type_) + { + m.c[0].r[0].u32 = v0; + m.c[0].vecsize = 1; + m.columns = 1; + } + + SPIRConstant(uint32_t constant_type_, uint32_t v0, uint32_t v1) + : constant_type(constant_type_) + { + m.c[0].r[0].u32 = v0; + m.c[0].r[1].u32 = v1; + m.c[0].vecsize = 2; + m.columns = 1; + } + + SPIRConstant(uint32_t constant_type_, uint32_t v0, uint32_t v1, uint32_t v2) + : constant_type(constant_type_) + { + m.c[0].r[0].u32 = v0; + m.c[0].r[1].u32 = v1; + m.c[0].r[2].u32 = v2; + m.c[0].vecsize = 3; + m.columns = 1; + } + + SPIRConstant(uint32_t constant_type_, uint32_t v0, uint32_t v1, uint32_t v2, uint32_t v3) + : constant_type(constant_type_) + { + m.c[0].r[0].u32 = v0; + m.c[0].r[1].u32 = v1; + m.c[0].r[2].u32 = v2; + m.c[0].r[3].u32 = v3; + m.c[0].vecsize = 4; + m.columns = 1; + } + + SPIRConstant(uint32_t constant_type_, uint64_t v0) + : constant_type(constant_type_) + { + m.c[0].r[0].u64 = v0; + m.c[0].vecsize = 1; + m.columns = 1; + } + + SPIRConstant(uint32_t constant_type_, uint64_t v0, uint64_t v1) + : constant_type(constant_type_) + { + m.c[0].r[0].u64 = v0; + m.c[0].r[1].u64 = v1; + m.c[0].vecsize = 2; + m.columns = 1; + } + + SPIRConstant(uint32_t constant_type_, uint64_t v0, uint64_t v1, uint64_t v2) + : constant_type(constant_type_) + { + m.c[0].r[0].u64 = v0; + m.c[0].r[1].u64 = v1; + m.c[0].r[2].u64 = v2; + m.c[0].vecsize = 3; + m.columns = 1; + } + + SPIRConstant(uint32_t constant_type_, uint64_t v0, uint64_t v1, uint64_t v2, uint64_t v3) + : constant_type(constant_type_) + { + m.c[0].r[0].u64 = v0; + m.c[0].r[1].u64 = v1; + m.c[0].r[2].u64 = v2; + m.c[0].r[3].u64 = v3; + m.c[0].vecsize = 4; + m.columns = 1; + } + + SPIRConstant(uint32_t constant_type_, const ConstantVector &vec0) + : constant_type(constant_type_) + { + m.columns = 1; + m.c[0] = vec0; + } + + SPIRConstant(uint32_t constant_type_, const ConstantVector &vec0, const ConstantVector &vec1) + : constant_type(constant_type_) + { + m.columns = 2; + m.c[0] = vec0; + m.c[1] = vec1; + } + + SPIRConstant(uint32_t constant_type_, const ConstantVector &vec0, const ConstantVector &vec1, + const ConstantVector &vec2) + : constant_type(constant_type_) + { + m.columns = 3; + m.c[0] = vec0; + m.c[1] = vec1; + m.c[2] = vec2; + } + + SPIRConstant(uint32_t constant_type_, const ConstantVector &vec0, const ConstantVector &vec1, + const ConstantVector &vec2, const ConstantVector &vec3) + : constant_type(constant_type_) + { + m.columns = 4; + m.c[0] = vec0; + m.c[1] = vec1; + m.c[2] = vec2; + m.c[3] = vec3; + } + + uint32_t constant_type; + ConstantMatrix m; + bool specialization = false; // If the constant is a specialization constant. + + // For composites which are constant arrays, etc. + std::vector subconstants; +}; + +class Variant +{ +public: + // MSVC 2013 workaround, we shouldn't need these constructors. + Variant() = default; + Variant(Variant &&other) + { + *this = std::move(other); + } + Variant &operator=(Variant &&other) + { + if (this != &other) + { + holder = move(other.holder); + type = other.type; + other.type = TypeNone; + } + return *this; + } + + void set(std::unique_ptr val, uint32_t new_type) + { + holder = std::move(val); + if (type != TypeNone && type != new_type) + SPIRV_CROSS_THROW("Overwriting a variant with new type."); + type = new_type; + } + + template + T &get() + { + if (!holder) + SPIRV_CROSS_THROW("nullptr"); + if (T::type != type) + SPIRV_CROSS_THROW("Bad cast"); + return *static_cast(holder.get()); + } + + template + const T &get() const + { + if (!holder) + SPIRV_CROSS_THROW("nullptr"); + if (T::type != type) + SPIRV_CROSS_THROW("Bad cast"); + return *static_cast(holder.get()); + } + + uint32_t get_type() const + { + return type; + } + bool empty() const + { + return !holder; + } + void reset() + { + holder.reset(); + type = TypeNone; + } + +private: + std::unique_ptr holder; + uint32_t type = TypeNone; +}; + +template +T &variant_get(Variant &var) +{ + return var.get(); +} + +template +const T &variant_get(const Variant &var) +{ + return var.get(); +} + +template +T &variant_set(Variant &var, P &&... args) +{ + auto uptr = std::unique_ptr(new T(std::forward

(args)...)); + auto ptr = uptr.get(); + var.set(std::move(uptr), T::type); + return *ptr; +} + +struct Meta +{ + struct Decoration + { + std::string alias; + std::string qualified_alias; + uint64_t decoration_flags = 0; + spv::BuiltIn builtin_type; + uint32_t location = 0; + uint32_t set = 0; + uint32_t binding = 0; + uint32_t offset = 0; + uint32_t array_stride = 0; + uint32_t matrix_stride = 0; + uint32_t input_attachment = 0; + uint32_t spec_id = 0; + bool builtin = false; + }; + + Decoration decoration; + std::vector members; + uint32_t sampler = 0; +}; + +// A user callback that remaps the type of any variable. +// var_name is the declared name of the variable. +// name_of_type is the textual name of the type which will be used in the code unless written to by the callback. +using VariableTypeRemapCallback = + std::function; + +class ClassicLocale +{ +public: + ClassicLocale() + { + old = std::locale::global(std::locale::classic()); + } + ~ClassicLocale() + { + std::locale::global(old); + } + +private: + std::locale old; +}; +} + +#endif diff --git a/libs/openFrameworks/vk/spirv-cross/include/spirv_cross.cpp b/libs/openFrameworks/vk/spirv-cross/include/spirv_cross.cpp new file mode 100644 index 00000000000..84b167b3840 --- /dev/null +++ b/libs/openFrameworks/vk/spirv-cross/include/spirv_cross.cpp @@ -0,0 +1,3336 @@ +/* + * Copyright 2015-2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "spirv_cross.hpp" +#include "GLSL.std.450.h" +#include "spirv_cfg.hpp" +#include +#include +#include + +using namespace std; +using namespace spv; +using namespace spirv_cross; + +#define log(...) fprintf(stderr, __VA_ARGS__) + +static string ensure_valid_identifier(const string &name) +{ + // Functions in glslangValidator are mangled with name( stuff. + // Normally, we would never see '(' in any legal identifiers, so just strip them out. + auto str = name.substr(0, name.find('(')); + + for (uint32_t i = 0; i < str.size(); i++) + { + auto &c = str[i]; + + // _ variables are reserved by the internal implementation, + // otherwise, make sure the name is a valid identifier. + if (i == 0 || (str[0] == '_' && i == 1)) + c = isalpha(c) ? c : '_'; + else + c = isalnum(c) ? c : '_'; + } + return str; +} + +Instruction::Instruction(const vector &spirv, uint32_t &index) +{ + op = spirv[index] & 0xffff; + count = (spirv[index] >> 16) & 0xffff; + + if (count == 0) + SPIRV_CROSS_THROW("SPIR-V instructions cannot consume 0 words. Invalid SPIR-V file."); + + offset = index + 1; + length = count - 1; + + index += count; + + if (index > spirv.size()) + SPIRV_CROSS_THROW("SPIR-V instruction goes out of bounds."); +} + +Compiler::Compiler(vector ir) + : spirv(move(ir)) +{ + parse(); +} + +Compiler::Compiler(const uint32_t *ir, size_t word_count) + : spirv(ir, ir + word_count) +{ + parse(); +} + +string Compiler::compile() +{ + // Force a classic "C" locale, reverts when function returns + ClassicLocale classic_locale; + return ""; +} + +bool Compiler::variable_storage_is_aliased(const SPIRVariable &v) +{ + auto &type = get(v.basetype); + bool ssbo = (meta[type.self].decoration.decoration_flags & (1ull << DecorationBufferBlock)) != 0; + bool image = type.basetype == SPIRType::Image; + bool counter = type.basetype == SPIRType::AtomicCounter; + bool is_restrict = (meta[v.self].decoration.decoration_flags & (1ull << DecorationRestrict)) != 0; + return !is_restrict && (ssbo || image || counter); +} + +bool Compiler::block_is_pure(const SPIRBlock &block) +{ + for (auto &i : block.ops) + { + auto ops = stream(i); + auto op = static_cast(i.op); + + switch (op) + { + case OpFunctionCall: + { + uint32_t func = ops[2]; + if (!function_is_pure(get(func))) + return false; + break; + } + + case OpCopyMemory: + case OpStore: + { + auto &type = expression_type(ops[0]); + if (type.storage != StorageClassFunction) + return false; + break; + } + + case OpImageWrite: + return false; + + // Atomics are impure. + case OpAtomicLoad: + case OpAtomicStore: + case OpAtomicExchange: + case OpAtomicCompareExchange: + case OpAtomicIIncrement: + case OpAtomicIDecrement: + case OpAtomicIAdd: + case OpAtomicISub: + case OpAtomicSMin: + case OpAtomicUMin: + case OpAtomicSMax: + case OpAtomicUMax: + case OpAtomicAnd: + case OpAtomicOr: + case OpAtomicXor: + return false; + + // Geometry shader builtins modify global state. + case OpEndPrimitive: + case OpEmitStreamVertex: + case OpEndStreamPrimitive: + case OpEmitVertex: + return false; + + // Barriers disallow any reordering, so we should treat blocks with barrier as writing. + case OpControlBarrier: + case OpMemoryBarrier: + return false; + + // OpExtInst is potentially impure depending on extension, but GLSL builtins are at least pure. + + default: + break; + } + } + + return true; +} + +string Compiler::to_name(uint32_t id, bool allow_alias) const +{ + if (allow_alias && ids.at(id).get_type() == TypeType) + { + // If this type is a simple alias, emit the + // name of the original type instead. + // We don't want to override the meta alias + // as that can be overridden by the reflection APIs after parse. + auto &type = get(id); + if (type.type_alias) + return to_name(type.type_alias); + } + + if (meta[id].decoration.alias.empty()) + return join("_", id); + else + return meta.at(id).decoration.alias; +} + +bool Compiler::function_is_pure(const SPIRFunction &func) +{ + for (auto block : func.blocks) + { + if (!block_is_pure(get(block))) + { + //fprintf(stderr, "Function %s is impure!\n", to_name(func.self).c_str()); + return false; + } + } + + //fprintf(stderr, "Function %s is pure!\n", to_name(func.self).c_str()); + return true; +} + +void Compiler::register_global_read_dependencies(const SPIRBlock &block, uint32_t id) +{ + for (auto &i : block.ops) + { + auto ops = stream(i); + auto op = static_cast(i.op); + + switch (op) + { + case OpFunctionCall: + { + uint32_t func = ops[2]; + register_global_read_dependencies(get(func), id); + break; + } + + case OpLoad: + case OpImageRead: + { + // If we're in a storage class which does not get invalidated, adding dependencies here is no big deal. + auto *var = maybe_get_backing_variable(ops[2]); + if (var && var->storage != StorageClassFunction) + { + auto &type = get(var->basetype); + + // InputTargets are immutable. + if (type.basetype != SPIRType::Image && type.image.dim != DimSubpassData) + var->dependees.push_back(id); + } + break; + } + + default: + break; + } + } +} + +void Compiler::register_global_read_dependencies(const SPIRFunction &func, uint32_t id) +{ + for (auto block : func.blocks) + register_global_read_dependencies(get(block), id); +} + +SPIRVariable *Compiler::maybe_get_backing_variable(uint32_t chain) +{ + auto *var = maybe_get(chain); + if (!var) + { + auto *cexpr = maybe_get(chain); + if (cexpr) + var = maybe_get(cexpr->loaded_from); + } + + return var; +} + +void Compiler::register_read(uint32_t expr, uint32_t chain, bool forwarded) +{ + auto &e = get(expr); + auto *var = maybe_get_backing_variable(chain); + + if (var) + { + e.loaded_from = var->self; + + // If the backing variable is immutable, we do not need to depend on the variable. + if (forwarded && !is_immutable(var->self)) + var->dependees.push_back(e.self); + + // If we load from a parameter, make sure we create "inout" if we also write to the parameter. + // The default is "in" however, so we never invalidate our compilation by reading. + if (var && var->parameter) + var->parameter->read_count++; + } +} + +void Compiler::register_write(uint32_t chain) +{ + auto *var = maybe_get(chain); + if (!var) + { + // If we're storing through an access chain, invalidate the backing variable instead. + auto *expr = maybe_get(chain); + if (expr && expr->loaded_from) + var = maybe_get(expr->loaded_from); + } + + if (var) + { + // If our variable is in a storage class which can alias with other buffers, + // invalidate all variables which depend on aliased variables. + if (variable_storage_is_aliased(*var)) + flush_all_aliased_variables(); + else if (var) + flush_dependees(*var); + + // We tried to write to a parameter which is not marked with out qualifier, force a recompile. + if (var->parameter && var->parameter->write_count == 0) + { + var->parameter->write_count++; + force_recompile = true; + } + } +} + +void Compiler::flush_dependees(SPIRVariable &var) +{ + for (auto expr : var.dependees) + invalid_expressions.insert(expr); + var.dependees.clear(); +} + +void Compiler::flush_all_aliased_variables() +{ + for (auto aliased : aliased_variables) + flush_dependees(get(aliased)); +} + +void Compiler::flush_all_atomic_capable_variables() +{ + for (auto global : global_variables) + flush_dependees(get(global)); + flush_all_aliased_variables(); +} + +void Compiler::flush_all_active_variables() +{ + // Invalidate all temporaries we read from variables in this block since they were forwarded. + // Invalidate all temporaries we read from globals. + for (auto &v : current_function->local_variables) + flush_dependees(get(v)); + for (auto &arg : current_function->arguments) + flush_dependees(get(arg.id)); + for (auto global : global_variables) + flush_dependees(get(global)); + + flush_all_aliased_variables(); +} + +const SPIRType &Compiler::expression_type(uint32_t id) const +{ + switch (ids[id].get_type()) + { + case TypeVariable: + return get(get(id).basetype); + + case TypeExpression: + return get(get(id).expression_type); + + case TypeConstant: + return get(get(id).constant_type); + + case TypeConstantOp: + return get(get(id).basetype); + + case TypeUndef: + return get(get(id).basetype); + + default: + SPIRV_CROSS_THROW("Cannot resolve expression type."); + } +} + +bool Compiler::expression_is_lvalue(uint32_t id) const +{ + auto &type = expression_type(id); + switch (type.basetype) + { + case SPIRType::SampledImage: + case SPIRType::Image: + case SPIRType::Sampler: + return false; + + default: + return true; + } +} + +bool Compiler::is_immutable(uint32_t id) const +{ + if (ids[id].get_type() == TypeVariable) + { + auto &var = get(id); + + // Anything we load from the UniformConstant address space is guaranteed to be immutable. + bool pointer_to_const = var.storage == StorageClassUniformConstant; + return pointer_to_const || var.phi_variable || !expression_is_lvalue(id); + } + else if (ids[id].get_type() == TypeExpression) + return get(id).immutable; + else if (ids[id].get_type() == TypeConstant || ids[id].get_type() == TypeConstantOp || + ids[id].get_type() == TypeUndef) + return true; + else + return false; +} + +static inline bool storage_class_is_interface(spv::StorageClass storage) +{ + switch (storage) + { + case StorageClassInput: + case StorageClassOutput: + case StorageClassUniform: + case StorageClassUniformConstant: + case StorageClassAtomicCounter: + case StorageClassPushConstant: + return true; + + default: + return false; + } +} + +bool Compiler::is_hidden_variable(const SPIRVariable &var, bool include_builtins) const +{ + if ((is_builtin_variable(var) && !include_builtins) || var.remapped_variable) + return true; + + // Combined image samplers are always considered active as they are "magic" variables. + if (find_if(begin(combined_image_samplers), end(combined_image_samplers), [&var](const CombinedImageSampler &samp) { + return samp.combined_id == var.self; + }) != end(combined_image_samplers)) + { + return false; + } + + bool hidden = false; + if (check_active_interface_variables && storage_class_is_interface(var.storage)) + hidden = active_interface_variables.find(var.self) == end(active_interface_variables); + return hidden; +} + +bool Compiler::is_builtin_variable(const SPIRVariable &var) const +{ + if (var.compat_builtin || meta[var.self].decoration.builtin) + return true; + + // We can have builtin structs as well. If one member of a struct is builtin, the struct must also be builtin. + for (auto &m : meta[get(var.basetype).self].members) + if (m.builtin) + return true; + + return false; +} + +bool Compiler::is_member_builtin(const SPIRType &type, uint32_t index, BuiltIn *builtin) const +{ + auto &memb = meta[type.self].members; + if (index < memb.size() && memb[index].builtin) + { + if (builtin) + *builtin = memb[index].builtin_type; + return true; + } + + return false; +} + +bool Compiler::is_scalar(const SPIRType &type) const +{ + return type.vecsize == 1 && type.columns == 1; +} + +bool Compiler::is_vector(const SPIRType &type) const +{ + return type.vecsize > 1 && type.columns == 1; +} + +bool Compiler::is_matrix(const SPIRType &type) const +{ + return type.vecsize > 1 && type.columns > 1; +} + +ShaderResources Compiler::get_shader_resources() const +{ + return get_shader_resources(nullptr); +} + +ShaderResources Compiler::get_shader_resources(const unordered_set &active_variables) const +{ + return get_shader_resources(&active_variables); +} + +bool Compiler::InterfaceVariableAccessHandler::handle(Op opcode, const uint32_t *args, uint32_t length) +{ + uint32_t variable = 0; + switch (opcode) + { + // Need this first, otherwise, GCC complains about unhandled switch statements. + default: + break; + + case OpFunctionCall: + { + // Invalid SPIR-V. + if (length < 3) + return false; + + uint32_t count = length - 3; + args += 3; + for (uint32_t i = 0; i < count; i++) + { + auto *var = compiler.maybe_get(args[i]); + if (var && storage_class_is_interface(var->storage)) + variables.insert(args[i]); + } + break; + } + + case OpAtomicStore: + case OpStore: + // Invalid SPIR-V. + if (length < 1) + return false; + variable = args[0]; + break; + + case OpCopyMemory: + { + if (length < 2) + return false; + + auto *var = compiler.maybe_get(args[0]); + if (var && storage_class_is_interface(var->storage)) + variables.insert(variable); + + var = compiler.maybe_get(args[1]); + if (var && storage_class_is_interface(var->storage)) + variables.insert(variable); + break; + } + + case OpAccessChain: + case OpInBoundsAccessChain: + case OpLoad: + case OpCopyObject: + case OpImageTexelPointer: + case OpAtomicLoad: + case OpAtomicExchange: + case OpAtomicCompareExchange: + case OpAtomicIIncrement: + case OpAtomicIDecrement: + case OpAtomicIAdd: + case OpAtomicISub: + case OpAtomicSMin: + case OpAtomicUMin: + case OpAtomicSMax: + case OpAtomicUMax: + case OpAtomicAnd: + case OpAtomicOr: + case OpAtomicXor: + // Invalid SPIR-V. + if (length < 3) + return false; + variable = args[2]; + break; + } + + if (variable) + { + auto *var = compiler.maybe_get(variable); + if (var && storage_class_is_interface(var->storage)) + variables.insert(variable); + } + return true; +} + +unordered_set Compiler::get_active_interface_variables() const +{ + // Traverse the call graph and find all interface variables which are in use. + unordered_set variables; + InterfaceVariableAccessHandler handler(*this, variables); + traverse_all_reachable_opcodes(get(entry_point), handler); + return variables; +} + +void Compiler::set_enabled_interface_variables(std::unordered_set active_variables) +{ + active_interface_variables = move(active_variables); + check_active_interface_variables = true; +} + +ShaderResources Compiler::get_shader_resources(const unordered_set *active_variables) const +{ + ShaderResources res; + + for (auto &id : ids) + { + if (id.get_type() != TypeVariable) + continue; + + auto &var = id.get(); + auto &type = get(var.basetype); + + // It is possible for uniform storage classes to be passed as function parameters, so detect + // that. To detect function parameters, check of StorageClass of variable is function scope. + if (var.storage == StorageClassFunction || !type.pointer || is_builtin_variable(var)) + continue; + + if (active_variables && active_variables->find(var.self) == end(*active_variables)) + continue; + + // Input + if (var.storage == StorageClassInput && interface_variable_exists_in_entry_point(var.self)) + { + if (meta[type.self].decoration.decoration_flags & (1ull << DecorationBlock)) + res.stage_inputs.push_back({ var.self, var.basetype, type.self, meta[type.self].decoration.alias }); + else + res.stage_inputs.push_back({ var.self, var.basetype, type.self, meta[var.self].decoration.alias }); + } + // Subpass inputs + else if (var.storage == StorageClassUniformConstant && type.image.dim == DimSubpassData) + { + res.subpass_inputs.push_back({ var.self, var.basetype, type.self, meta[var.self].decoration.alias }); + } + // Outputs + else if (var.storage == StorageClassOutput && interface_variable_exists_in_entry_point(var.self)) + { + if (meta[type.self].decoration.decoration_flags & (1ull << DecorationBlock)) + res.stage_outputs.push_back({ var.self, var.basetype, type.self, meta[type.self].decoration.alias }); + else + res.stage_outputs.push_back({ var.self, var.basetype, type.self, meta[var.self].decoration.alias }); + } + // UBOs + else if (type.storage == StorageClassUniform && + (meta[type.self].decoration.decoration_flags & (1ull << DecorationBlock))) + { + res.uniform_buffers.push_back({ var.self, var.basetype, type.self, meta[type.self].decoration.alias }); + } + // SSBOs + else if (type.storage == StorageClassUniform && + (meta[type.self].decoration.decoration_flags & (1ull << DecorationBufferBlock))) + { + res.storage_buffers.push_back({ var.self, var.basetype, type.self, meta[type.self].decoration.alias }); + } + // Push constant blocks + else if (type.storage == StorageClassPushConstant) + { + // There can only be one push constant block, but keep the vector in case this restriction is lifted + // in the future. + res.push_constant_buffers.push_back({ var.self, var.basetype, type.self, meta[var.self].decoration.alias }); + } + // Images + else if (type.storage == StorageClassUniformConstant && type.basetype == SPIRType::Image && + type.image.sampled == 2) + { + res.storage_images.push_back({ var.self, var.basetype, type.self, meta[var.self].decoration.alias }); + } + // Separate images + else if (type.storage == StorageClassUniformConstant && type.basetype == SPIRType::Image && + type.image.sampled == 1) + { + res.separate_images.push_back({ var.self, var.basetype, type.self, meta[var.self].decoration.alias }); + } + // Separate samplers + else if (type.storage == StorageClassUniformConstant && type.basetype == SPIRType::Sampler) + { + res.separate_samplers.push_back({ var.self, var.basetype, type.self, meta[var.self].decoration.alias }); + } + // Textures + else if (type.storage == StorageClassUniformConstant && type.basetype == SPIRType::SampledImage) + { + res.sampled_images.push_back({ var.self, var.basetype, type.self, meta[var.self].decoration.alias }); + } + // Atomic counters + else if (type.storage == StorageClassAtomicCounter) + { + res.atomic_counters.push_back({ var.self, var.basetype, type.self, meta[var.self].decoration.alias }); + } + } + + return res; +} + +static inline uint32_t swap_endian(uint32_t v) +{ + return ((v >> 24) & 0x000000ffu) | ((v >> 8) & 0x0000ff00u) | ((v << 8) & 0x00ff0000u) | ((v << 24) & 0xff000000u); +} + +static string extract_string(const vector &spirv, uint32_t offset) +{ + string ret; + for (uint32_t i = offset; i < spirv.size(); i++) + { + uint32_t w = spirv[i]; + + for (uint32_t j = 0; j < 4; j++, w >>= 8) + { + char c = w & 0xff; + if (c == '\0') + return ret; + ret += c; + } + } + + SPIRV_CROSS_THROW("String was not terminated before EOF"); +} + +static bool is_valid_spirv_version(uint32_t version) +{ + switch (version) + { + // Allow v99 since it tends to just work. + case 99: + case 0x10000: // SPIR-V 1.0 + case 0x10100: // SPIR-V 1.1 + return true; + + default: + return false; + } +} + +void Compiler::parse() +{ + auto len = spirv.size(); + if (len < 5) + SPIRV_CROSS_THROW("SPIRV file too small."); + + auto s = spirv.data(); + + // Endian-swap if we need to. + if (s[0] == swap_endian(MagicNumber)) + transform(begin(spirv), end(spirv), begin(spirv), [](uint32_t c) { return swap_endian(c); }); + + if (s[0] != MagicNumber || !is_valid_spirv_version(s[1])) + SPIRV_CROSS_THROW("Invalid SPIRV format."); + + uint32_t bound = s[3]; + ids.resize(bound); + meta.resize(bound); + + uint32_t offset = 5; + while (offset < len) + inst.emplace_back(spirv, offset); + + for (auto &i : inst) + parse(i); + + if (current_function) + SPIRV_CROSS_THROW("Function was not terminated."); + if (current_block) + SPIRV_CROSS_THROW("Block was not terminated."); +} + +void Compiler::flatten_interface_block(uint32_t id) +{ + auto &var = get(id); + auto &type = get(var.basetype); + auto flags = meta.at(type.self).decoration.decoration_flags; + + if (!type.array.empty()) + SPIRV_CROSS_THROW("Type is array of UBOs."); + if (type.basetype != SPIRType::Struct) + SPIRV_CROSS_THROW("Type is not a struct."); + if ((flags & (1ull << DecorationBlock)) == 0) + SPIRV_CROSS_THROW("Type is not a block."); + if (type.member_types.empty()) + SPIRV_CROSS_THROW("Member list of struct is empty."); + + uint32_t t = type.member_types[0]; + for (auto &m : type.member_types) + if (t != m) + SPIRV_CROSS_THROW("Types in block differ."); + + auto &mtype = get(t); + if (!mtype.array.empty()) + SPIRV_CROSS_THROW("Member type cannot be arrays."); + if (mtype.basetype == SPIRType::Struct) + SPIRV_CROSS_THROW("Member type cannot be struct."); + + // Inherit variable name from interface block name. + meta.at(var.self).decoration.alias = meta.at(type.self).decoration.alias; + + auto storage = var.storage; + if (storage == StorageClassUniform) + storage = StorageClassUniformConstant; + + // Change type definition in-place into an array instead. + // Access chains will still work as-is. + uint32_t array_size = uint32_t(type.member_types.size()); + type = mtype; + type.array.push_back(array_size); + type.pointer = true; + type.storage = storage; + var.storage = storage; +} + +void Compiler::update_name_cache(unordered_set &cache, string &name) +{ + if (name.empty()) + return; + + if (cache.find(name) == end(cache)) + { + cache.insert(name); + return; + } + + uint32_t counter = 0; + auto tmpname = name; + + // If there is a collision (very rare), + // keep tacking on extra identifier until it's unique. + do + { + counter++; + name = tmpname + "_" + convert_to_string(counter); + } while (cache.find(name) != end(cache)); + cache.insert(name); +} + +void Compiler::set_name(uint32_t id, const std::string &name) +{ + auto &str = meta.at(id).decoration.alias; + str.clear(); + + if (name.empty()) + return; + // Reserved for temporaries. + if (name[0] == '_' && name.size() >= 2 && isdigit(name[1])) + return; + + str = ensure_valid_identifier(name); +} + +const SPIRType &Compiler::get_type(uint32_t id) const +{ + return get(id); +} + +const SPIRType &Compiler::get_type_from_variable(uint32_t id) const +{ + return get(get(id).basetype); +} + +void Compiler::set_member_decoration(uint32_t id, uint32_t index, Decoration decoration, uint32_t argument) +{ + meta.at(id).members.resize(max(meta[id].members.size(), size_t(index) + 1)); + auto &dec = meta.at(id).members[index]; + dec.decoration_flags |= 1ull << decoration; + + switch (decoration) + { + case DecorationBuiltIn: + dec.builtin = true; + dec.builtin_type = static_cast(argument); + break; + + case DecorationLocation: + dec.location = argument; + break; + + case DecorationBinding: + dec.binding = argument; + break; + + case DecorationOffset: + dec.offset = argument; + break; + + case DecorationSpecId: + dec.spec_id = argument; + break; + + case DecorationMatrixStride: + dec.matrix_stride = argument; + break; + + default: + break; + } +} + +void Compiler::set_member_name(uint32_t id, uint32_t index, const std::string &name) +{ + meta.at(id).members.resize(max(meta[id].members.size(), size_t(index) + 1)); + + auto &str = meta.at(id).members[index].alias; + str.clear(); + if (name.empty()) + return; + + // Reserved for unnamed members. + if (name[0] == '_' && name.size() >= 2 && isdigit(name[1])) + return; + + str = ensure_valid_identifier(name); +} + +const std::string &Compiler::get_member_name(uint32_t id, uint32_t index) const +{ + auto &m = meta.at(id); + if (index >= m.members.size()) + { + static string empty; + return empty; + } + + return m.members[index].alias; +} + +void Compiler::set_member_qualified_name(uint32_t id, uint32_t index, const std::string &name) +{ + meta.at(id).members.resize(max(meta[id].members.size(), size_t(index) + 1)); + meta.at(id).members[index].qualified_alias = name; +} + +uint32_t Compiler::get_member_decoration(uint32_t id, uint32_t index, Decoration decoration) const +{ + auto &m = meta.at(id); + if (index >= m.members.size()) + return 0; + + auto &dec = m.members[index]; + if (!(dec.decoration_flags & (1ull << decoration))) + return 0; + + switch (decoration) + { + case DecorationBuiltIn: + return dec.builtin_type; + case DecorationLocation: + return dec.location; + case DecorationBinding: + return dec.binding; + case DecorationOffset: + return dec.offset; + case DecorationSpecId: + return dec.spec_id; + default: + return 1; + } +} + +uint64_t Compiler::get_member_decoration_mask(uint32_t id, uint32_t index) const +{ + auto &m = meta.at(id); + if (index >= m.members.size()) + return 0; + + return m.members[index].decoration_flags; +} + +bool Compiler::has_member_decoration(uint32_t id, uint32_t index, Decoration decoration) const +{ + return get_member_decoration_mask(id, index) & (1ull << decoration); +} + +void Compiler::unset_member_decoration(uint32_t id, uint32_t index, Decoration decoration) +{ + auto &m = meta.at(id); + if (index >= m.members.size()) + return; + + auto &dec = m.members[index]; + + dec.decoration_flags &= ~(1ull << decoration); + switch (decoration) + { + case DecorationBuiltIn: + dec.builtin = false; + break; + + case DecorationLocation: + dec.location = 0; + break; + + case DecorationOffset: + dec.offset = 0; + break; + + case DecorationSpecId: + dec.spec_id = 0; + break; + + default: + break; + } +} + +void Compiler::set_decoration(uint32_t id, Decoration decoration, uint32_t argument) +{ + auto &dec = meta.at(id).decoration; + dec.decoration_flags |= 1ull << decoration; + + switch (decoration) + { + case DecorationBuiltIn: + dec.builtin = true; + dec.builtin_type = static_cast(argument); + break; + + case DecorationLocation: + dec.location = argument; + break; + + case DecorationOffset: + dec.offset = argument; + break; + + case DecorationArrayStride: + dec.array_stride = argument; + break; + + case DecorationMatrixStride: + dec.matrix_stride = argument; + break; + + case DecorationBinding: + dec.binding = argument; + break; + + case DecorationDescriptorSet: + dec.set = argument; + break; + + case DecorationInputAttachmentIndex: + dec.input_attachment = argument; + break; + + case DecorationSpecId: + dec.spec_id = argument; + break; + + default: + break; + } +} + +StorageClass Compiler::get_storage_class(uint32_t id) const +{ + return get(id).storage; +} + +const std::string &Compiler::get_name(uint32_t id) const +{ + return meta.at(id).decoration.alias; +} + +uint64_t Compiler::get_decoration_mask(uint32_t id) const +{ + auto &dec = meta.at(id).decoration; + return dec.decoration_flags; +} + +bool Compiler::has_decoration(uint32_t id, Decoration decoration) const +{ + return get_decoration_mask(id) & (1ull << decoration); +} + +uint32_t Compiler::get_decoration(uint32_t id, Decoration decoration) const +{ + auto &dec = meta.at(id).decoration; + if (!(dec.decoration_flags & (1ull << decoration))) + return 0; + + switch (decoration) + { + case DecorationBuiltIn: + return dec.builtin_type; + case DecorationLocation: + return dec.location; + case DecorationOffset: + return dec.offset; + case DecorationBinding: + return dec.binding; + case DecorationDescriptorSet: + return dec.set; + case DecorationInputAttachmentIndex: + return dec.input_attachment; + case DecorationSpecId: + return dec.spec_id; + case DecorationArrayStride: + return dec.array_stride; + case DecorationMatrixStride: + return dec.matrix_stride; + default: + return 1; + } +} + +void Compiler::unset_decoration(uint32_t id, Decoration decoration) +{ + auto &dec = meta.at(id).decoration; + dec.decoration_flags &= ~(1ull << decoration); + switch (decoration) + { + case DecorationBuiltIn: + dec.builtin = false; + break; + + case DecorationLocation: + dec.location = 0; + break; + + case DecorationOffset: + dec.offset = 0; + break; + + case DecorationBinding: + dec.binding = 0; + break; + + case DecorationDescriptorSet: + dec.set = 0; + break; + + case DecorationInputAttachmentIndex: + dec.input_attachment = 0; + break; + + case DecorationSpecId: + dec.spec_id = 0; + break; + + default: + break; + } +} + +void Compiler::parse(const Instruction &instruction) +{ + auto ops = stream(instruction); + auto op = static_cast(instruction.op); + uint32_t length = instruction.length; + + switch (op) + { + case OpMemoryModel: + case OpSourceExtension: + case OpNop: + case OpLine: + break; + + case OpSource: + { + auto lang = static_cast(ops[0]); + switch (lang) + { + case SourceLanguageESSL: + source.es = true; + source.version = ops[1]; + source.known = true; + break; + + case SourceLanguageGLSL: + source.es = false; + source.version = ops[1]; + source.known = true; + break; + + default: + source.known = false; + break; + } + break; + } + + case OpUndef: + { + uint32_t result_type = ops[0]; + uint32_t id = ops[1]; + set(id, result_type); + break; + } + + case OpCapability: + { + uint32_t cap = ops[0]; + if (cap == CapabilityKernel) + SPIRV_CROSS_THROW("Kernel capability not supported."); + break; + } + + case OpExtension: + // Ignore extensions + break; + + case OpExtInstImport: + { + uint32_t id = ops[0]; + auto ext = extract_string(spirv, instruction.offset + 1); + if (ext == "GLSL.std.450") + set(id, SPIRExtension::GLSL); + else + set(id, SPIRExtension::Unsupported); + + // Other SPIR-V extensions currently not supported. + + break; + } + + case OpEntryPoint: + { + auto itr = + entry_points.insert(make_pair(ops[1], SPIREntryPoint(ops[1], static_cast(ops[0]), + extract_string(spirv, instruction.offset + 2)))); + auto &e = itr.first->second; + + // Strings need nul-terminator and consume the whole word. + uint32_t strlen_words = uint32_t((e.name.size() + 1 + 3) >> 2); + e.interface_variables.insert(end(e.interface_variables), ops + strlen_words + 2, ops + instruction.length); + + // Set the name of the entry point in case OpName is not provided later + set_name(ops[1], e.name); + + // If we don't have an entry, make the first one our "default". + if (!entry_point) + entry_point = ops[1]; + break; + } + + case OpExecutionMode: + { + auto &execution = entry_points[ops[0]]; + auto mode = static_cast(ops[1]); + execution.flags |= 1ull << mode; + + switch (mode) + { + case ExecutionModeInvocations: + execution.invocations = ops[2]; + break; + + case ExecutionModeLocalSize: + execution.workgroup_size.x = ops[2]; + execution.workgroup_size.y = ops[3]; + execution.workgroup_size.z = ops[4]; + break; + + case ExecutionModeOutputVertices: + execution.output_vertices = ops[2]; + break; + + default: + break; + } + break; + } + + case OpName: + { + uint32_t id = ops[0]; + set_name(id, extract_string(spirv, instruction.offset + 1)); + break; + } + + case OpMemberName: + { + uint32_t id = ops[0]; + uint32_t member = ops[1]; + set_member_name(id, member, extract_string(spirv, instruction.offset + 2)); + break; + } + + case OpDecorate: + { + uint32_t id = ops[0]; + + auto decoration = static_cast(ops[1]); + if (length >= 3) + set_decoration(id, decoration, ops[2]); + else + set_decoration(id, decoration); + + break; + } + + case OpMemberDecorate: + { + uint32_t id = ops[0]; + uint32_t member = ops[1]; + auto decoration = static_cast(ops[2]); + if (length >= 4) + set_member_decoration(id, member, decoration, ops[3]); + else + set_member_decoration(id, member, decoration); + break; + } + + // Build up basic types. + case OpTypeVoid: + { + uint32_t id = ops[0]; + auto &type = set(id); + type.basetype = SPIRType::Void; + break; + } + + case OpTypeBool: + { + uint32_t id = ops[0]; + auto &type = set(id); + type.basetype = SPIRType::Boolean; + type.width = 1; + break; + } + + case OpTypeFloat: + { + uint32_t id = ops[0]; + uint32_t width = ops[1]; + auto &type = set(id); + type.basetype = width > 32 ? SPIRType::Double : SPIRType::Float; + type.width = width; + break; + } + + case OpTypeInt: + { + uint32_t id = ops[0]; + uint32_t width = ops[1]; + auto &type = set(id); + type.basetype = + ops[2] ? (width > 32 ? SPIRType::Int64 : SPIRType::Int) : (width > 32 ? SPIRType::UInt64 : SPIRType::UInt); + type.width = width; + break; + } + + // Build composite types by "inheriting". + // NOTE: The self member is also copied! For pointers and array modifiers this is a good thing + // since we can refer to decorations on pointee classes which is needed for UBO/SSBO, I/O blocks in geometry/tess etc. + case OpTypeVector: + { + uint32_t id = ops[0]; + uint32_t vecsize = ops[2]; + + auto &base = get(ops[1]); + auto &vecbase = set(id); + + vecbase = base; + vecbase.vecsize = vecsize; + vecbase.self = id; + vecbase.parent_type = ops[1]; + break; + } + + case OpTypeMatrix: + { + uint32_t id = ops[0]; + uint32_t colcount = ops[2]; + + auto &base = get(ops[1]); + auto &matrixbase = set(id); + + matrixbase = base; + matrixbase.columns = colcount; + matrixbase.self = id; + matrixbase.parent_type = ops[1]; + break; + } + + case OpTypeArray: + { + uint32_t id = ops[0]; + + auto &base = get(ops[1]); + auto &arraybase = set(id); + + arraybase = base; + + auto *c = maybe_get(ops[2]); + bool literal = c && !c->specialization; + + arraybase.array_size_literal.push_back(literal); + arraybase.array.push_back(literal ? c->scalar() : ops[2]); + arraybase.parent_type = ops[1]; + // Do NOT set arraybase.self! + break; + } + + case OpTypeRuntimeArray: + { + uint32_t id = ops[0]; + + auto &base = get(ops[1]); + auto &arraybase = set(id); + + arraybase = base; + arraybase.array.push_back(0); + arraybase.array_size_literal.push_back(true); + arraybase.parent_type = ops[1]; + // Do NOT set arraybase.self! + break; + } + + case OpTypeImage: + { + uint32_t id = ops[0]; + auto &type = set(id); + type.basetype = SPIRType::Image; + type.image.type = ops[1]; + type.image.dim = static_cast(ops[2]); + type.image.depth = ops[3] != 0; + type.image.arrayed = ops[4] != 0; + type.image.ms = ops[5] != 0; + type.image.sampled = ops[6]; + type.image.format = static_cast(ops[7]); + break; + } + + case OpTypeSampledImage: + { + uint32_t id = ops[0]; + uint32_t imagetype = ops[1]; + auto &type = set(id); + type = get(imagetype); + type.basetype = SPIRType::SampledImage; + type.self = id; + break; + } + + case OpTypeSampler: + { + uint32_t id = ops[0]; + auto &type = set(id); + type.basetype = SPIRType::Sampler; + break; + } + + case OpTypePointer: + { + uint32_t id = ops[0]; + + auto &base = get(ops[2]); + auto &ptrbase = set(id); + + ptrbase = base; + if (ptrbase.pointer) + SPIRV_CROSS_THROW("Cannot make pointer-to-pointer type."); + ptrbase.pointer = true; + ptrbase.storage = static_cast(ops[1]); + + if (ptrbase.storage == StorageClassAtomicCounter) + ptrbase.basetype = SPIRType::AtomicCounter; + + ptrbase.parent_type = ops[2]; + + // Do NOT set ptrbase.self! + break; + } + + case OpTypeStruct: + { + uint32_t id = ops[0]; + auto &type = set(id); + type.basetype = SPIRType::Struct; + for (uint32_t i = 1; i < length; i++) + type.member_types.push_back(ops[i]); + + // Check if we have seen this struct type before, with just different + // decorations. + // + // Add workaround for issue #17 as well by looking at OpName for the struct + // types, which we shouldn't normally do. + // We should not normally have to consider type aliases like this to begin with + // however ... glslang issues #304, #307 cover this. + for (auto &other : global_struct_cache) + { + if (get_name(type.self) == get_name(other) && types_are_logically_equivalent(type, get(other))) + { + type.type_alias = other; + break; + } + } + + if (type.type_alias == 0) + global_struct_cache.push_back(id); + break; + } + + case OpTypeFunction: + { + uint32_t id = ops[0]; + uint32_t ret = ops[1]; + + auto &func = set(id, ret); + for (uint32_t i = 2; i < length; i++) + func.parameter_types.push_back(ops[i]); + break; + } + + // Variable declaration + // All variables are essentially pointers with a storage qualifier. + case OpVariable: + { + uint32_t type = ops[0]; + uint32_t id = ops[1]; + auto storage = static_cast(ops[2]); + uint32_t initializer = length == 4 ? ops[3] : 0; + + if (storage == StorageClassFunction) + { + if (!current_function) + SPIRV_CROSS_THROW("No function currently in scope"); + current_function->add_local_variable(id); + } + else if (storage == StorageClassPrivate || storage == StorageClassWorkgroup || storage == StorageClassOutput) + { + global_variables.push_back(id); + } + + auto &var = set(id, type, storage, initializer); + + if (variable_storage_is_aliased(var)) + aliased_variables.push_back(var.self); + + break; + } + + // OpPhi + // OpPhi is a fairly magical opcode. + // It selects temporary variables based on which parent block we *came from*. + // In high-level languages we can "de-SSA" by creating a function local, and flush out temporaries to this function-local + // variable to emulate SSA Phi. + case OpPhi: + { + if (!current_function) + SPIRV_CROSS_THROW("No function currently in scope"); + if (!current_block) + SPIRV_CROSS_THROW("No block currently in scope"); + + uint32_t result_type = ops[0]; + uint32_t id = ops[1]; + + // Instead of a temporary, create a new function-wide temporary with this ID instead. + auto &var = set(id, result_type, spv::StorageClassFunction); + var.phi_variable = true; + + current_function->add_local_variable(id); + + for (uint32_t i = 2; i + 2 <= length; i += 2) + current_block->phi_variables.push_back({ ops[i], ops[i + 1], id }); + break; + } + + // Constants + case OpSpecConstant: + case OpConstant: + { + uint32_t id = ops[1]; + auto &type = get(ops[0]); + if (type.width > 32) + set(id, ops[0], ops[2] | (uint64_t(ops[3]) << 32)).specialization = op == OpSpecConstant; + else + set(id, ops[0], ops[2]).specialization = op == OpSpecConstant; + break; + } + + case OpSpecConstantFalse: + case OpConstantFalse: + { + uint32_t id = ops[1]; + set(id, ops[0], uint32_t(0)).specialization = op == OpSpecConstantFalse; + break; + } + + case OpSpecConstantTrue: + case OpConstantTrue: + { + uint32_t id = ops[1]; + set(id, ops[0], uint32_t(1)).specialization = op == OpSpecConstantTrue; + break; + } + + case OpSpecConstantComposite: + case OpConstantComposite: + { + uint32_t id = ops[1]; + uint32_t type = ops[0]; + + auto &ctype = get(type); + SPIRConstant *constant = nullptr; + + // We can have constants which are structs and arrays. + // In this case, our SPIRConstant will be a list of other SPIRConstant ids which we + // can refer to. + if (ctype.basetype == SPIRType::Struct || !ctype.array.empty()) + { + constant = &set(id, type, ops + 2, length - 2); + constant->specialization = op == OpSpecConstantComposite; + break; + } + + bool type_64bit = ctype.width > 32; + bool matrix = ctype.columns > 1; + + if (matrix) + { + switch (length - 2) + { + case 1: + constant = &set(id, type, get(ops[2]).vector()); + break; + + case 2: + constant = &set(id, type, get(ops[2]).vector(), + get(ops[3]).vector()); + break; + + case 3: + constant = &set(id, type, get(ops[2]).vector(), + get(ops[3]).vector(), get(ops[4]).vector()); + break; + + case 4: + constant = + &set(id, type, get(ops[2]).vector(), get(ops[3]).vector(), + get(ops[4]).vector(), get(ops[5]).vector()); + break; + + default: + SPIRV_CROSS_THROW("OpConstantComposite only supports 1, 2, 3 and 4 columns."); + } + } + else + { + switch (length - 2) + { + case 1: + if (type_64bit) + constant = &set(id, type, get(ops[2]).scalar_u64()); + else + constant = &set(id, type, get(ops[2]).scalar()); + break; + + case 2: + if (type_64bit) + { + constant = &set(id, type, get(ops[2]).scalar_u64(), + get(ops[3]).scalar_u64()); + } + else + { + constant = &set(id, type, get(ops[2]).scalar(), + get(ops[3]).scalar()); + } + break; + + case 3: + if (type_64bit) + { + constant = &set(id, type, get(ops[2]).scalar_u64(), + get(ops[3]).scalar_u64(), + get(ops[4]).scalar_u64()); + } + else + { + constant = + &set(id, type, get(ops[2]).scalar(), + get(ops[3]).scalar(), get(ops[4]).scalar()); + } + break; + + case 4: + if (type_64bit) + { + constant = &set( + id, type, get(ops[2]).scalar_u64(), get(ops[3]).scalar_u64(), + get(ops[4]).scalar_u64(), get(ops[5]).scalar_u64()); + } + else + { + constant = &set( + id, type, get(ops[2]).scalar(), get(ops[3]).scalar(), + get(ops[4]).scalar(), get(ops[5]).scalar()); + } + break; + + default: + SPIRV_CROSS_THROW("OpConstantComposite only supports 1, 2, 3 and 4 components."); + } + } + + constant->specialization = op == OpSpecConstantComposite; + break; + } + + // Functions + case OpFunction: + { + uint32_t res = ops[0]; + uint32_t id = ops[1]; + // Control + uint32_t type = ops[3]; + + if (current_function) + SPIRV_CROSS_THROW("Must end a function before starting a new one!"); + + current_function = &set(id, res, type); + break; + } + + case OpFunctionParameter: + { + uint32_t type = ops[0]; + uint32_t id = ops[1]; + + if (!current_function) + SPIRV_CROSS_THROW("Must be in a function!"); + + current_function->add_parameter(type, id); + set(id, type, StorageClassFunction); + break; + } + + case OpFunctionEnd: + { + if (current_block) + { + // Very specific error message, but seems to come up quite often. + SPIRV_CROSS_THROW( + "Cannot end a function before ending the current block.\n" + "Likely cause: If this SPIR-V was created from glslang HLSL, make sure the entry point is valid."); + } + current_function = nullptr; + break; + } + + // Blocks + case OpLabel: + { + // OpLabel always starts a block. + if (!current_function) + SPIRV_CROSS_THROW("Blocks cannot exist outside functions!"); + + uint32_t id = ops[0]; + + current_function->blocks.push_back(id); + if (!current_function->entry_block) + current_function->entry_block = id; + + if (current_block) + SPIRV_CROSS_THROW("Cannot start a block before ending the current block."); + + current_block = &set(id); + break; + } + + // Branch instructions end blocks. + case OpBranch: + { + if (!current_block) + SPIRV_CROSS_THROW("Trying to end a non-existing block."); + + uint32_t target = ops[0]; + current_block->terminator = SPIRBlock::Direct; + current_block->next_block = target; + current_block = nullptr; + break; + } + + case OpBranchConditional: + { + if (!current_block) + SPIRV_CROSS_THROW("Trying to end a non-existing block."); + + current_block->condition = ops[0]; + current_block->true_block = ops[1]; + current_block->false_block = ops[2]; + + current_block->terminator = SPIRBlock::Select; + current_block = nullptr; + break; + } + + case OpSwitch: + { + if (!current_block) + SPIRV_CROSS_THROW("Trying to end a non-existing block."); + + if (current_block->merge == SPIRBlock::MergeNone) + SPIRV_CROSS_THROW("Switch statement is not structured"); + + current_block->terminator = SPIRBlock::MultiSelect; + + current_block->condition = ops[0]; + current_block->default_block = ops[1]; + + for (uint32_t i = 2; i + 2 <= length; i += 2) + current_block->cases.push_back({ ops[i], ops[i + 1] }); + + // If we jump to next block, make it break instead since we're inside a switch case block at that point. + multiselect_merge_targets.insert(current_block->next_block); + + current_block = nullptr; + break; + } + + case OpKill: + { + if (!current_block) + SPIRV_CROSS_THROW("Trying to end a non-existing block."); + current_block->terminator = SPIRBlock::Kill; + current_block = nullptr; + break; + } + + case OpReturn: + { + if (!current_block) + SPIRV_CROSS_THROW("Trying to end a non-existing block."); + current_block->terminator = SPIRBlock::Return; + current_block = nullptr; + break; + } + + case OpReturnValue: + { + if (!current_block) + SPIRV_CROSS_THROW("Trying to end a non-existing block."); + current_block->terminator = SPIRBlock::Return; + current_block->return_value = ops[0]; + current_block = nullptr; + break; + } + + case OpUnreachable: + { + if (!current_block) + SPIRV_CROSS_THROW("Trying to end a non-existing block."); + current_block->terminator = SPIRBlock::Unreachable; + current_block = nullptr; + break; + } + + case OpSelectionMerge: + { + if (!current_block) + SPIRV_CROSS_THROW("Trying to modify a non-existing block."); + + current_block->next_block = ops[0]; + current_block->merge = SPIRBlock::MergeSelection; + selection_merge_targets.insert(current_block->next_block); + break; + } + + case OpLoopMerge: + { + if (!current_block) + SPIRV_CROSS_THROW("Trying to modify a non-existing block."); + + current_block->merge_block = ops[0]; + current_block->continue_block = ops[1]; + current_block->merge = SPIRBlock::MergeLoop; + + loop_blocks.insert(current_block->self); + loop_merge_targets.insert(current_block->merge_block); + + // Don't add loop headers to continue blocks, + // which would make it impossible branch into the loop header since + // they are treated as continues. + if (current_block->continue_block != current_block->self) + continue_blocks.insert(current_block->continue_block); + break; + } + + case OpSpecConstantOp: + { + if (length < 3) + SPIRV_CROSS_THROW("OpSpecConstantOp not enough arguments."); + + uint32_t result_type = ops[0]; + uint32_t id = ops[1]; + auto spec_op = static_cast(ops[2]); + + set(id, result_type, spec_op, ops + 3, length - 3); + break; + } + + // Actual opcodes. + default: + { + if (!current_block) + SPIRV_CROSS_THROW("Currently no block to insert opcode."); + + current_block->ops.push_back(instruction); + break; + } + } +} + +bool Compiler::block_is_loop_candidate(const SPIRBlock &block, SPIRBlock::Method method) const +{ + // Tried and failed. + if (block.disable_block_optimization || block.complex_continue) + return false; + + if (method == SPIRBlock::MergeToSelectForLoop) + { + // Try to detect common for loop pattern + // which the code backend can use to create cleaner code. + // for(;;) { if (cond) { some_body; } else { break; } } + // is the pattern we're looking for. + bool ret = block.terminator == SPIRBlock::Select && block.merge == SPIRBlock::MergeLoop && + block.true_block != block.merge_block && block.true_block != block.self && + block.false_block == block.merge_block; + + // If we have OpPhi which depends on branches which came from our own block, + // we need to flush phi variables in else block instead of a trivial break, + // so we cannot assume this is a for loop candidate. + if (ret) + { + for (auto &phi : block.phi_variables) + if (phi.parent == block.self) + return false; + + auto *merge = maybe_get(block.merge_block); + if (merge) + for (auto &phi : merge->phi_variables) + if (phi.parent == block.self) + return false; + } + return ret; + } + else if (method == SPIRBlock::MergeToDirectForLoop) + { + // Empty loop header that just sets up merge target + // and branches to loop body. + bool ret = block.terminator == SPIRBlock::Direct && block.merge == SPIRBlock::MergeLoop && block.ops.empty(); + + if (!ret) + return false; + + auto &child = get(block.next_block); + ret = child.terminator == SPIRBlock::Select && child.merge == SPIRBlock::MergeNone && + child.false_block == block.merge_block && child.true_block != block.merge_block && + child.true_block != block.self; + + // If we have OpPhi which depends on branches which came from our own block, + // we need to flush phi variables in else block instead of a trivial break, + // so we cannot assume this is a for loop candidate. + if (ret) + { + for (auto &phi : block.phi_variables) + if (phi.parent == block.self || phi.parent == child.self) + return false; + + for (auto &phi : child.phi_variables) + if (phi.parent == block.self) + return false; + + auto *merge = maybe_get(block.merge_block); + if (merge) + for (auto &phi : merge->phi_variables) + if (phi.parent == block.self || phi.parent == child.false_block) + return false; + } + + return ret; + } + else + return false; +} + +bool Compiler::block_is_outside_flow_control_from_block(const SPIRBlock &from, const SPIRBlock &to) +{ + auto *start = &from; + + if (start->self == to.self) + return true; + + // Break cycles. + if (is_continue(start->self)) + return false; + + // If our select block doesn't merge, we must break or continue in these blocks, + // so if continues occur branchless within these blocks, consider them branchless as well. + // This is typically used for loop control. + if (start->terminator == SPIRBlock::Select && start->merge == SPIRBlock::MergeNone && + (block_is_outside_flow_control_from_block(get(start->true_block), to) || + block_is_outside_flow_control_from_block(get(start->false_block), to))) + { + return true; + } + else if (start->merge_block && block_is_outside_flow_control_from_block(get(start->merge_block), to)) + { + return true; + } + else if (start->next_block && block_is_outside_flow_control_from_block(get(start->next_block), to)) + { + return true; + } + else + return false; +} + +bool Compiler::execution_is_noop(const SPIRBlock &from, const SPIRBlock &to) const +{ + if (!execution_is_branchless(from, to)) + return false; + + auto *start = &from; + for (;;) + { + if (start->self == to.self) + return true; + + if (!start->ops.empty()) + return false; + + start = &get(start->next_block); + } +} + +bool Compiler::execution_is_branchless(const SPIRBlock &from, const SPIRBlock &to) const +{ + auto *start = &from; + for (;;) + { + if (start->self == to.self) + return true; + + if (start->terminator == SPIRBlock::Direct && start->merge == SPIRBlock::MergeNone) + start = &get(start->next_block); + else + return false; + } +} + +SPIRBlock::ContinueBlockType Compiler::continue_block_type(const SPIRBlock &block) const +{ + // The block was deemed too complex during code emit, pick conservative fallback paths. + if (block.complex_continue) + return SPIRBlock::ComplexLoop; + + // In older glslang output continue block can be equal to the loop header. + // In this case, execution is clearly branchless, so just assume a while loop header here. + if (block.merge == SPIRBlock::MergeLoop) + return SPIRBlock::WhileLoop; + + auto &dominator = get(block.loop_dominator); + + if (execution_is_noop(block, dominator)) + return SPIRBlock::WhileLoop; + else if (execution_is_branchless(block, dominator)) + return SPIRBlock::ForLoop; + else + { + if (block.merge == SPIRBlock::MergeNone && block.terminator == SPIRBlock::Select && + block.true_block == dominator.self && block.false_block == dominator.merge_block) + { + return SPIRBlock::DoWhileLoop; + } + else + return SPIRBlock::ComplexLoop; + } +} + +bool Compiler::traverse_all_reachable_opcodes(const SPIRBlock &block, OpcodeHandler &handler) const +{ + handler.set_current_block(block); + + // Ideally, perhaps traverse the CFG instead of all blocks in order to eliminate dead blocks, + // but this shouldn't be a problem in practice unless the SPIR-V is doing insane things like recursing + // inside dead blocks ... + for (auto &i : block.ops) + { + auto ops = stream(i); + auto op = static_cast(i.op); + + if (!handler.handle(op, ops, i.length)) + return false; + + if (op == OpFunctionCall) + { + auto &func = get(ops[2]); + if (handler.follow_function_call(func)) + { + if (!handler.begin_function_scope(ops, i.length)) + return false; + if (!traverse_all_reachable_opcodes(get(ops[2]), handler)) + return false; + if (!handler.end_function_scope(ops, i.length)) + return false; + } + } + } + + return true; +} + +bool Compiler::traverse_all_reachable_opcodes(const SPIRFunction &func, OpcodeHandler &handler) const +{ + for (auto block : func.blocks) + if (!traverse_all_reachable_opcodes(get(block), handler)) + return false; + + return true; +} + +uint32_t Compiler::type_struct_member_offset(const SPIRType &type, uint32_t index) const +{ + // Decoration must be set in valid SPIR-V, otherwise throw. + auto &dec = meta[type.self].members.at(index); + if (dec.decoration_flags & (1ull << DecorationOffset)) + return dec.offset; + else + SPIRV_CROSS_THROW("Struct member does not have Offset set."); +} + +uint32_t Compiler::type_struct_member_array_stride(const SPIRType &type, uint32_t index) const +{ + // Decoration must be set in valid SPIR-V, otherwise throw. + // ArrayStride is part of the array type not OpMemberDecorate. + auto &dec = meta[type.member_types[index]].decoration; + if (dec.decoration_flags & (1ull << DecorationArrayStride)) + return dec.array_stride; + else + SPIRV_CROSS_THROW("Struct member does not have ArrayStride set."); +} + +uint32_t Compiler::type_struct_member_matrix_stride(const SPIRType &type, uint32_t index) const +{ + // Decoration must be set in valid SPIR-V, otherwise throw. + // MatrixStride is part of OpMemberDecorate. + auto &dec = meta[type.self].members[index]; + if (dec.decoration_flags & (1ull << DecorationMatrixStride)) + return dec.matrix_stride; + else + SPIRV_CROSS_THROW("Struct member does not have MatrixStride set."); +} + +size_t Compiler::get_declared_struct_size(const SPIRType &type) const +{ + uint32_t last = uint32_t(type.member_types.size() - 1); + size_t offset = type_struct_member_offset(type, last); + size_t size = get_declared_struct_member_size(type, last); + return offset + size; +} + +size_t Compiler::get_declared_struct_member_size(const SPIRType &struct_type, uint32_t index) const +{ + auto flags = get_member_decoration_mask(struct_type.self, index); + auto &type = get(struct_type.member_types[index]); + + switch (type.basetype) + { + case SPIRType::Unknown: + case SPIRType::Void: + case SPIRType::Boolean: // Bools are purely logical, and cannot be used for externally visible types. + case SPIRType::AtomicCounter: + case SPIRType::Image: + case SPIRType::SampledImage: + case SPIRType::Sampler: + SPIRV_CROSS_THROW("Querying size for object with opaque size."); + + default: + break; + } + + if (!type.array.empty()) + { + // For arrays, we can use ArrayStride to get an easy check. + return type_struct_member_array_stride(struct_type, index) * type.array.back(); + } + else if (type.basetype == SPIRType::Struct) + { + return get_declared_struct_size(type); + } + else + { + unsigned vecsize = type.vecsize; + unsigned columns = type.columns; + + // Vectors. + if (columns == 1) + { + size_t component_size = type.width / 8; + return vecsize * component_size; + } + else + { + uint32_t matrix_stride = type_struct_member_matrix_stride(struct_type, index); + + // Per SPIR-V spec, matrices must be tightly packed and aligned up for vec3 accesses. + if (flags & (1ull << DecorationRowMajor)) + return matrix_stride * vecsize; + else if (flags & (1ull << DecorationColMajor)) + return matrix_stride * columns; + else + SPIRV_CROSS_THROW("Either row-major or column-major must be declared for matrices."); + } + } +} + +bool Compiler::BufferAccessHandler::handle(Op opcode, const uint32_t *args, uint32_t length) +{ + if (opcode != OpAccessChain && opcode != OpInBoundsAccessChain) + return true; + + // Invalid SPIR-V. + if (length < 4) + return false; + + if (args[2] != id) + return true; + + // Don't bother traversing the entire access chain tree yet. + // If we access a struct member, assume we access the entire member. + uint32_t index = compiler.get(args[3]).scalar(); + + // Seen this index already. + if (seen.find(index) != end(seen)) + return true; + seen.insert(index); + + auto &type = compiler.expression_type(id); + uint32_t offset = compiler.type_struct_member_offset(type, index); + + size_t range; + // If we have another member in the struct, deduce the range by looking at the next member. + // This is okay since structs in SPIR-V can have padding, but Offset decoration must be + // monotonically increasing. + // Of course, this doesn't take into account if the SPIR-V for some reason decided to add + // very large amounts of padding, but that's not really a big deal. + if (index + 1 < type.member_types.size()) + { + range = compiler.type_struct_member_offset(type, index + 1) - offset; + } + else + { + // No padding, so just deduce it from the size of the member directly. + range = compiler.get_declared_struct_member_size(type, index); + } + + ranges.push_back({ index, offset, range }); + return true; +} + +std::vector Compiler::get_active_buffer_ranges(uint32_t id) const +{ + std::vector ranges; + BufferAccessHandler handler(*this, ranges, id); + traverse_all_reachable_opcodes(get(entry_point), handler); + return ranges; +} + +// Increase the number of IDs by the specified incremental amount. +// Returns the value of the first ID available for use in the expanded bound. +uint32_t Compiler::increase_bound_by(uint32_t incr_amount) +{ + auto curr_bound = ids.size(); + auto new_bound = curr_bound + incr_amount; + ids.resize(new_bound); + meta.resize(new_bound); + return uint32_t(curr_bound); +} + +bool Compiler::types_are_logically_equivalent(const SPIRType &a, const SPIRType &b) const +{ + if (a.basetype != b.basetype) + return false; + if (a.width != b.width) + return false; + if (a.vecsize != b.vecsize) + return false; + if (a.columns != b.columns) + return false; + if (a.array.size() != b.array.size()) + return false; + + size_t array_count = a.array.size(); + if (array_count && memcmp(a.array.data(), b.array.data(), array_count * sizeof(uint32_t)) != 0) + return false; + + if (a.basetype == SPIRType::Image || a.basetype == SPIRType::SampledImage) + { + if (memcmp(&a.image, &b.image, sizeof(SPIRType::Image)) != 0) + return false; + } + + if (a.member_types.size() != b.member_types.size()) + return false; + + size_t member_types = a.member_types.size(); + for (size_t i = 0; i < member_types; i++) + { + if (!types_are_logically_equivalent(get(a.member_types[i]), get(b.member_types[i]))) + return false; + } + + return true; +} + +uint64_t Compiler::get_execution_mode_mask() const +{ + return get_entry_point().flags; +} + +void Compiler::set_execution_mode(ExecutionMode mode, uint32_t arg0, uint32_t arg1, uint32_t arg2) +{ + auto &execution = get_entry_point(); + + execution.flags |= 1ull << mode; + switch (mode) + { + case ExecutionModeLocalSize: + execution.workgroup_size.x = arg0; + execution.workgroup_size.y = arg1; + execution.workgroup_size.z = arg2; + break; + + case ExecutionModeInvocations: + execution.invocations = arg0; + break; + + case ExecutionModeOutputVertices: + execution.output_vertices = arg0; + break; + + default: + break; + } +} + +void Compiler::unset_execution_mode(ExecutionMode mode) +{ + auto &execution = get_entry_point(); + execution.flags &= ~(1ull << mode); +} + +uint32_t Compiler::get_execution_mode_argument(spv::ExecutionMode mode, uint32_t index) const +{ + auto &execution = get_entry_point(); + switch (mode) + { + case ExecutionModeLocalSize: + switch (index) + { + case 0: + return execution.workgroup_size.x; + case 1: + return execution.workgroup_size.y; + case 2: + return execution.workgroup_size.z; + default: + return 0; + } + + case ExecutionModeInvocations: + return execution.invocations; + + case ExecutionModeOutputVertices: + return execution.output_vertices; + + default: + return 0; + } +} + +ExecutionModel Compiler::get_execution_model() const +{ + auto &execution = get_entry_point(); + return execution.model; +} + +void Compiler::set_remapped_variable_state(uint32_t id, bool remap_enable) +{ + get(id).remapped_variable = remap_enable; +} + +bool Compiler::get_remapped_variable_state(uint32_t id) const +{ + return get(id).remapped_variable; +} + +void Compiler::set_subpass_input_remapped_components(uint32_t id, uint32_t components) +{ + get(id).remapped_components = components; +} + +uint32_t Compiler::get_subpass_input_remapped_components(uint32_t id) const +{ + return get(id).remapped_components; +} + +void Compiler::inherit_expression_dependencies(uint32_t dst, uint32_t source_expression) +{ + // Don't inherit any expression dependencies if the expression in dst + // is not a forwarded temporary. + if (forwarded_temporaries.find(dst) == end(forwarded_temporaries) || + forced_temporaries.find(dst) != end(forced_temporaries)) + { + return; + } + + auto &e = get(dst); + auto *s = maybe_get(source_expression); + if (!s) + return; + + auto &e_deps = e.expression_dependencies; + auto &s_deps = s->expression_dependencies; + + // If we depend on a expression, we also depend on all sub-dependencies from source. + e_deps.push_back(source_expression); + e_deps.insert(end(e_deps), begin(s_deps), end(s_deps)); + + // Eliminate duplicated dependencies. + e_deps.erase(unique(begin(e_deps), end(e_deps)), end(e_deps)); +} + +vector Compiler::get_entry_points() const +{ + vector entries; + for (auto &entry : entry_points) + entries.push_back(entry.second.name); + return entries; +} + +void Compiler::set_entry_point(const std::string &name) +{ + auto &entry = get_entry_point(name); + entry_point = entry.self; +} + +SPIREntryPoint &Compiler::get_entry_point(const std::string &name) +{ + auto itr = + find_if(begin(entry_points), end(entry_points), + [&](const std::pair &entry) -> bool { return entry.second.name == name; }); + + if (itr == end(entry_points)) + SPIRV_CROSS_THROW("Entry point does not exist."); + + return itr->second; +} + +const SPIREntryPoint &Compiler::get_entry_point(const std::string &name) const +{ + auto itr = + find_if(begin(entry_points), end(entry_points), + [&](const std::pair &entry) -> bool { return entry.second.name == name; }); + + if (itr == end(entry_points)) + SPIRV_CROSS_THROW("Entry point does not exist."); + + return itr->second; +} + +const SPIREntryPoint &Compiler::get_entry_point() const +{ + return entry_points.find(entry_point)->second; +} + +SPIREntryPoint &Compiler::get_entry_point() +{ + return entry_points.find(entry_point)->second; +} + +bool Compiler::interface_variable_exists_in_entry_point(uint32_t id) const +{ + auto &var = get(id); + if (var.storage != StorageClassInput && var.storage != StorageClassOutput && + var.storage != StorageClassUniformConstant) + SPIRV_CROSS_THROW("Only Input, Output variables and Uniform constants are part of a shader linking interface."); + + // This is to avoid potential problems with very old glslang versions which did + // not emit input/output interfaces properly. + // We can assume they only had a single entry point, and single entry point + // shaders could easily be assumed to use every interface variable anyways. + if (entry_points.size() <= 1) + return true; + + auto &execution = get_entry_point(); + return find(begin(execution.interface_variables), end(execution.interface_variables), id) != + end(execution.interface_variables); +} + +void Compiler::CombinedImageSamplerHandler::push_remap_parameters(const SPIRFunction &func, const uint32_t *args, + uint32_t length) +{ + // If possible, pipe through a remapping table so that parameters know + // which variables they actually bind to in this scope. + unordered_map remapping; + for (uint32_t i = 0; i < length; i++) + remapping[func.arguments[i].id] = remap_parameter(args[i]); + parameter_remapping.push(move(remapping)); +} + +void Compiler::CombinedImageSamplerHandler::pop_remap_parameters() +{ + parameter_remapping.pop(); +} + +uint32_t Compiler::CombinedImageSamplerHandler::remap_parameter(uint32_t id) +{ + auto *var = compiler.maybe_get_backing_variable(id); + if (var) + id = var->self; + + if (parameter_remapping.empty()) + return id; + + auto &remapping = parameter_remapping.top(); + auto itr = remapping.find(id); + if (itr != end(remapping)) + return itr->second; + else + return id; +} + +bool Compiler::CombinedImageSamplerHandler::begin_function_scope(const uint32_t *args, uint32_t length) +{ + if (length < 3) + return false; + + auto &callee = compiler.get(args[2]); + args += 3; + length -= 3; + push_remap_parameters(callee, args, length); + functions.push(&callee); + return true; +} + +bool Compiler::CombinedImageSamplerHandler::end_function_scope(const uint32_t *args, uint32_t length) +{ + if (length < 3) + return false; + + auto &callee = compiler.get(args[2]); + args += 3; + length -= 3; + + // There are two types of cases we have to handle, + // a callee might call sampler2D(texture2D, sampler) directly where + // one or more parameters originate from parameters. + // Alternatively, we need to provide combined image samplers to our callees, + // and in this case we need to add those as well. + + pop_remap_parameters(); + + // Our callee has now been processed at least once. + // No point in doing it again. + callee.do_combined_parameters = false; + + auto ¶ms = functions.top()->combined_parameters; + functions.pop(); + if (functions.empty()) + return true; + + auto &caller = *functions.top(); + if (caller.do_combined_parameters) + { + for (auto ¶m : params) + { + uint32_t image_id = param.global_image ? param.image_id : args[param.image_id]; + uint32_t sampler_id = param.global_sampler ? param.sampler_id : args[param.sampler_id]; + + auto *i = compiler.maybe_get_backing_variable(image_id); + auto *s = compiler.maybe_get_backing_variable(sampler_id); + if (i) + image_id = i->self; + if (s) + sampler_id = s->self; + + register_combined_image_sampler(caller, image_id, sampler_id); + } + } + + return true; +} + +void Compiler::CombinedImageSamplerHandler::register_combined_image_sampler(SPIRFunction &caller, uint32_t image_id, + uint32_t sampler_id) +{ + // We now have a texture ID and a sampler ID which will either be found as a global + // or a parameter in our own function. If both are global, they will not need a parameter, + // otherwise, add it to our list. + SPIRFunction::CombinedImageSamplerParameter param = { + 0u, image_id, sampler_id, true, true, + }; + + auto texture_itr = find_if(begin(caller.arguments), end(caller.arguments), + [image_id](const SPIRFunction::Parameter &p) { return p.id == image_id; }); + auto sampler_itr = find_if(begin(caller.arguments), end(caller.arguments), + [sampler_id](const SPIRFunction::Parameter &p) { return p.id == sampler_id; }); + + if (texture_itr != end(caller.arguments)) + { + param.global_image = false; + param.image_id = uint32_t(texture_itr - begin(caller.arguments)); + } + + if (sampler_itr != end(caller.arguments)) + { + param.global_sampler = false; + param.sampler_id = uint32_t(sampler_itr - begin(caller.arguments)); + } + + if (param.global_image && param.global_sampler) + return; + + auto itr = find_if(begin(caller.combined_parameters), end(caller.combined_parameters), + [¶m](const SPIRFunction::CombinedImageSamplerParameter &p) { + return param.image_id == p.image_id && param.sampler_id == p.sampler_id && + param.global_image == p.global_image && param.global_sampler == p.global_sampler; + }); + + if (itr == end(caller.combined_parameters)) + { + uint32_t id = compiler.increase_bound_by(3); + auto type_id = id + 0; + auto ptr_type_id = id + 1; + auto combined_id = id + 2; + auto &base = compiler.expression_type(image_id); + auto &type = compiler.set(type_id); + auto &ptr_type = compiler.set(ptr_type_id); + + type = base; + type.self = type_id; + type.basetype = SPIRType::SampledImage; + type.pointer = false; + type.storage = StorageClassGeneric; + + ptr_type = type; + ptr_type.pointer = true; + ptr_type.storage = StorageClassUniformConstant; + + // Build new variable. + compiler.set(combined_id, ptr_type_id, StorageClassFunction, 0); + + // Inherit RelaxedPrecision (and potentially other useful flags if deemed relevant). + auto &new_flags = compiler.meta[combined_id].decoration.decoration_flags; + auto old_flags = compiler.meta[sampler_id].decoration.decoration_flags; + new_flags = old_flags & (1ull << DecorationRelaxedPrecision); + + param.id = combined_id; + + compiler.set_name(combined_id, + join("SPIRV_Cross_Combined", compiler.to_name(image_id), compiler.to_name(sampler_id))); + + caller.combined_parameters.push_back(param); + caller.shadow_arguments.push_back({ ptr_type_id, combined_id, 0u, 0u, true }); + } +} + +bool Compiler::CombinedImageSamplerHandler::handle(Op opcode, const uint32_t *args, uint32_t length) +{ + // We need to figure out where samplers and images are loaded from, so do only the bare bones compilation we need. + switch (opcode) + { + case OpLoad: + { + if (length < 3) + return false; + + uint32_t result_type = args[0]; + + auto &type = compiler.get(result_type); + bool separate_image = type.basetype == SPIRType::Image && type.image.sampled == 1; + bool separate_sampler = type.basetype == SPIRType::Sampler; + + // If not separate image or sampler, don't bother. + if (!separate_image && !separate_sampler) + return true; + + uint32_t id = args[1]; + uint32_t ptr = args[2]; + compiler.set(id, "", result_type, true); + compiler.register_read(id, ptr, true); + return true; + } + + case OpInBoundsAccessChain: + case OpAccessChain: + { + if (length < 3) + return false; + + // Technically, it is possible to have arrays of textures and arrays of samplers and combine them, but this becomes essentially + // impossible to implement, since we don't know which concrete sampler we are accessing. + // One potential way is to create a combinatorial explosion where N textures and M samplers are combined into N * M sampler2Ds, + // but this seems ridiculously complicated for a problem which is easy to work around. + // Checking access chains like this assumes we don't have samplers or textures inside uniform structs, but this makes no sense. + + auto &type = compiler.get(args[0]); + bool separate_image = type.basetype == SPIRType::Image && type.image.sampled == 1; + bool separate_sampler = type.basetype == SPIRType::Sampler; + if (separate_image) + SPIRV_CROSS_THROW( + "Attempting to use arrays of separate images. This is not possible to statically remap to plain GLSL."); + if (separate_sampler) + SPIRV_CROSS_THROW("Attempting to use arrays of separate samplers. This is not possible to statically " + "remap to plain GLSL."); + return true; + } + + case OpSampledImage: + // Do it outside. + break; + + default: + return true; + } + + if (length < 4) + return false; + + // Registers sampler2D calls used in case they are parameters so + // that their callees know which combined image samplers to propagate down the call stack. + if (!functions.empty()) + { + auto &callee = *functions.top(); + if (callee.do_combined_parameters) + { + uint32_t image_id = args[2]; + + auto *image = compiler.maybe_get_backing_variable(image_id); + if (image) + image_id = image->self; + + uint32_t sampler_id = args[3]; + auto *sampler = compiler.maybe_get_backing_variable(sampler_id); + if (sampler) + sampler_id = sampler->self; + + register_combined_image_sampler(callee, image_id, sampler_id); + } + } + + // For function calls, we need to remap IDs which are function parameters into global variables. + // This information is statically known from the current place in the call stack. + // Function parameters are not necessarily pointers, so if we don't have a backing variable, remapping will know + // which backing variable the image/sample came from. + uint32_t image_id = remap_parameter(args[2]); + uint32_t sampler_id = remap_parameter(args[3]); + + auto itr = find_if(begin(compiler.combined_image_samplers), end(compiler.combined_image_samplers), + [image_id, sampler_id](const CombinedImageSampler &combined) { + return combined.image_id == image_id && combined.sampler_id == sampler_id; + }); + + if (itr == end(compiler.combined_image_samplers)) + { + auto id = compiler.increase_bound_by(2); + auto type_id = id + 0; + auto combined_id = id + 1; + auto sampled_type = args[0]; + + // Make a new type, pointer to OpTypeSampledImage, so we can make a variable of this type. + // We will probably have this type lying around, but it doesn't hurt to make duplicates for internal purposes. + auto &type = compiler.set(type_id); + auto &base = compiler.get(sampled_type); + type = base; + type.pointer = true; + type.storage = StorageClassUniformConstant; + + // Build new variable. + compiler.set(combined_id, type_id, StorageClassUniformConstant, 0); + + // Inherit RelaxedPrecision (and potentially other useful flags if deemed relevant). + auto &new_flags = compiler.meta[combined_id].decoration.decoration_flags; + auto old_flags = compiler.meta[sampler_id].decoration.decoration_flags; + new_flags = old_flags & (1ull << DecorationRelaxedPrecision); + + compiler.combined_image_samplers.push_back({ combined_id, image_id, sampler_id }); + } + + return true; +} + +void Compiler::build_combined_image_samplers() +{ + for (auto &id : ids) + { + if (id.get_type() == TypeFunction) + { + auto &func = id.get(); + func.combined_parameters.clear(); + func.shadow_arguments.clear(); + func.do_combined_parameters = true; + } + } + + combined_image_samplers.clear(); + CombinedImageSamplerHandler handler(*this); + traverse_all_reachable_opcodes(get(entry_point), handler); +} + +vector Compiler::get_specialization_constants() const +{ + vector spec_consts; + for (auto &id : ids) + { + if (id.get_type() == TypeConstant) + { + auto &c = id.get(); + if (c.specialization) + { + spec_consts.push_back({ c.self, get_decoration(c.self, DecorationSpecId) }); + } + } + } + return spec_consts; +} + +SPIRConstant &Compiler::get_constant(uint32_t id) +{ + return get(id); +} + +const SPIRConstant &Compiler::get_constant(uint32_t id) const +{ + return get(id); +} + +static bool exists_unaccessed_path_to_return(const CFG &cfg, uint32_t block, const unordered_set &blocks) +{ + // This block accesses the variable. + if (blocks.find(block) != end(blocks)) + return false; + + // We are at the end of the CFG. + if (cfg.get_succeeding_edges(block).empty()) + return true; + + // If any of our successors have a path to the end, there exists a path from block. + for (auto &succ : cfg.get_succeeding_edges(block)) + if (exists_unaccessed_path_to_return(cfg, succ, blocks)) + return true; + + return false; +} + +void Compiler::analyze_parameter_preservation( + SPIRFunction &entry, const CFG &cfg, const unordered_map> &variable_to_blocks) +{ + for (auto &arg : entry.arguments) + { + // Non-pointers are always inputs. + auto &type = get(arg.type); + if (!type.pointer) + continue; + + // Opaque argument types are always in + bool potential_preserve; + switch (type.basetype) + { + case SPIRType::Sampler: + case SPIRType::Image: + case SPIRType::SampledImage: + case SPIRType::AtomicCounter: + potential_preserve = false; + break; + + default: + potential_preserve = true; + break; + } + + if (!potential_preserve) + continue; + + auto itr = variable_to_blocks.find(arg.id); + if (itr == end(variable_to_blocks)) + { + // Variable is never accessed. + continue; + } + + // If there is a path through the CFG where no block writes to the variable, the variable will be in an undefined state + // when the function returns. We therefore need to implicitly preserve the variable in case there are writers in the function. + // Major case here is if a function is + // void foo(int &var) { if (cond) var = 10; } + // Using read/write counts, we will think it's just an out variable, but it really needs to be inout, + // because if we don't write anything whatever we put into the function must return back to the caller. + if (exists_unaccessed_path_to_return(cfg, entry.entry_block, itr->second)) + arg.read_count++; + } +} + +void Compiler::analyze_variable_scope(SPIRFunction &entry) +{ + struct AccessHandler : OpcodeHandler + { + public: + AccessHandler(Compiler &compiler_) + : compiler(compiler_) + { + } + + bool follow_function_call(const SPIRFunction &) + { + // Only analyze within this function. + return false; + } + + void set_current_block(const SPIRBlock &block) + { + current_block = █ + + // If we're branching to a block which uses OpPhi, in GLSL + // this will be a variable write when we branch, + // so we need to track access to these variables as well to + // have a complete picture. + const auto test_phi = [this, &block](uint32_t to) { + auto &next = compiler.get(to); + for (auto &phi : next.phi_variables) + if (phi.parent == block.self) + accessed_variables_to_block[phi.function_variable].insert(block.self); + }; + + switch (block.terminator) + { + case SPIRBlock::Direct: + test_phi(block.next_block); + break; + + case SPIRBlock::Select: + test_phi(block.true_block); + test_phi(block.false_block); + break; + + case SPIRBlock::MultiSelect: + for (auto &target : block.cases) + test_phi(target.block); + if (block.default_block) + test_phi(block.default_block); + break; + + default: + break; + } + } + + bool handle(spv::Op op, const uint32_t *args, uint32_t length) + { + switch (op) + { + case OpStore: + { + if (length < 2) + return false; + + uint32_t ptr = args[0]; + auto *var = compiler.maybe_get_backing_variable(ptr); + if (var && var->storage == StorageClassFunction) + accessed_variables_to_block[var->self].insert(current_block->self); + break; + } + + case OpAccessChain: + case OpInBoundsAccessChain: + { + if (length < 3) + return false; + + uint32_t ptr = args[2]; + auto *var = compiler.maybe_get(ptr); + if (var && var->storage == StorageClassFunction) + accessed_variables_to_block[var->self].insert(current_block->self); + break; + } + + case OpCopyMemory: + { + if (length < 2) + return false; + + uint32_t lhs = args[0]; + uint32_t rhs = args[1]; + auto *var = compiler.maybe_get_backing_variable(lhs); + if (var && var->storage == StorageClassFunction) + accessed_variables_to_block[var->self].insert(current_block->self); + + var = compiler.maybe_get_backing_variable(rhs); + if (var && var->storage == StorageClassFunction) + accessed_variables_to_block[var->self].insert(current_block->self); + break; + } + + case OpCopyObject: + { + if (length < 3) + return false; + + auto *var = compiler.maybe_get_backing_variable(args[2]); + if (var && var->storage == StorageClassFunction) + accessed_variables_to_block[var->self].insert(current_block->self); + break; + } + + case OpLoad: + { + if (length < 3) + return false; + uint32_t ptr = args[2]; + auto *var = compiler.maybe_get_backing_variable(ptr); + if (var && var->storage == StorageClassFunction) + accessed_variables_to_block[var->self].insert(current_block->self); + break; + } + + case OpFunctionCall: + { + if (length < 3) + return false; + + length -= 3; + args += 3; + for (uint32_t i = 0; i < length; i++) + { + auto *var = compiler.maybe_get_backing_variable(args[i]); + if (var && var->storage == StorageClassFunction) + accessed_variables_to_block[var->self].insert(current_block->self); + } + break; + } + + case OpPhi: + { + if (length < 2) + return false; + + // Phi nodes are implemented as function variables, so register an access here. + accessed_variables_to_block[args[1]].insert(current_block->self); + break; + } + + // Atomics shouldn't be able to access function-local variables. + // Some GLSL builtins access a pointer. + + default: + break; + } + return true; + } + + Compiler &compiler; + std::unordered_map> accessed_variables_to_block; + const SPIRBlock *current_block = nullptr; + } handler(*this); + + // First, we map out all variable access within a function. + // Essentially a map of block -> { variables accessed in the basic block } + this->traverse_all_reachable_opcodes(entry, handler); + + // Compute the control flow graph for this function. + CFG cfg(*this, entry); + + // Analyze if there are parameters which need to be implicitly preserved with an "in" qualifier. + analyze_parameter_preservation(entry, cfg, handler.accessed_variables_to_block); + + unordered_map potential_loop_variables; + + // For each variable which is statically accessed. + for (auto &var : handler.accessed_variables_to_block) + { + DominatorBuilder builder(cfg); + auto &blocks = var.second; + auto &type = this->expression_type(var.first); + + // Figure out which block is dominating all accesses of those variables. + for (auto &block : blocks) + { + // If we're accessing a variable inside a continue block, this variable might be a loop variable. + // We can only use loop variables with scalars, as we cannot track static expressions for vectors. + if (this->is_continue(block) && type.vecsize == 1 && type.columns == 1) + { + // The variable is used in multiple continue blocks, this is not a loop + // candidate, signal that by setting block to -1u. + auto &potential = potential_loop_variables[var.first]; + + if (potential == 0) + potential = block; + else + potential = ~(0u); + } + builder.add_block(block); + } + + builder.lift_continue_block_dominator(); + + // Add it to a per-block list of variables. + uint32_t dominating_block = builder.get_dominator(); + // If all blocks here are dead code, this will be 0, so the variable in question + // will be completely eliminated. + if (dominating_block) + { + auto &block = this->get(dominating_block); + block.dominated_variables.push_back(var.first); + this->get(var.first).dominator = dominating_block; + } + } + + // Now, try to analyze whether or not these variables are actually loop variables. + for (auto &loop_variable : potential_loop_variables) + { + auto &var = this->get(loop_variable.first); + auto dominator = var.dominator; + auto block = loop_variable.second; + + // The variable was accessed in multiple continue blocks, ignore. + if (block == ~(0u) || block == 0) + continue; + + // Dead code. + if (dominator == 0) + continue; + + uint32_t header = 0; + + // Find the loop header for this block. + for (auto b : this->loop_blocks) + { + auto &potential_header = this->get(b); + if (potential_header.continue_block == block) + { + header = b; + break; + } + } + + assert(header); + auto &header_block = this->get(header); + + // Now, there are two conditions we need to meet for the variable to be a loop variable. + // 1. The dominating block must have a branch-free path to the loop header, + // this way we statically know which expression should be part of the loop variable initializer. + + // Walk from the dominator, if there is one straight edge connecting + // dominator and loop header, we statically know the loop initializer. + bool static_loop_init = true; + while (dominator != header) + { + auto &succ = cfg.get_succeeding_edges(dominator); + if (succ.size() != 1) + { + static_loop_init = false; + break; + } + + auto &pred = cfg.get_preceding_edges(succ.front()); + if (pred.size() != 1 || pred.front() != dominator) + { + static_loop_init = false; + break; + } + + dominator = succ.front(); + } + + if (!static_loop_init) + continue; + + // The second condition we need to meet is that no access after the loop + // merge can occur. Walk the CFG to see if we find anything. + auto &blocks = handler.accessed_variables_to_block[loop_variable.first]; + cfg.walk_from(header_block.merge_block, [&](uint32_t walk_block) { + // We found a block which accesses the variable outside the loop. + if (blocks.find(walk_block) != end(blocks)) + static_loop_init = false; + }); + + if (!static_loop_init) + continue; + + // We have a loop variable. + header_block.loop_variables.push_back(loop_variable.first); + // Need to sort here as variables come from an unordered container, and pushing stuff in wrong order + // will break reproducability in regression runs. + sort(begin(header_block.loop_variables), end(header_block.loop_variables)); + this->get(loop_variable.first).loop_variable = true; + } +} + +uint64_t Compiler::get_buffer_block_flags(const SPIRVariable &var) +{ + auto &type = get(var.basetype); + assert(type.basetype == SPIRType::Struct); + + // Some flags like non-writable, non-readable are actually found + // as member decorations. If all members have a decoration set, propagate + // the decoration up as a regular variable decoration. + uint64_t base_flags = meta[var.self].decoration.decoration_flags; + + if (type.member_types.empty()) + return base_flags; + + uint64_t all_members_flag_mask = ~(0ull); + for (uint32_t i = 0; i < uint32_t(type.member_types.size()); i++) + all_members_flag_mask &= get_member_decoration_mask(type.self, i); + + return base_flags | all_members_flag_mask; +} + +bool Compiler::get_common_basic_type(const SPIRType &type, SPIRType::BaseType &base_type) +{ + if (type.basetype == SPIRType::Struct) + { + base_type = SPIRType::Unknown; + for (auto &member_type : type.member_types) + { + SPIRType::BaseType member_base; + if (!get_common_basic_type(get(member_type), member_base)) + return false; + + if (base_type == SPIRType::Unknown) + base_type = member_base; + else if (base_type != member_base) + return false; + } + return true; + } + else + { + base_type = type.basetype; + return true; + } +} + +bool Compiler::ActiveBuiltinHandler::handle(spv::Op opcode, const uint32_t *args, uint32_t length) +{ + const auto add_if_builtin = [&](uint32_t id) { + // Only handles variables here. + // Builtins which are part of a block are handled in AccessChain. + auto *var = compiler.maybe_get(id); + if (var && compiler.meta[id].decoration.builtin) + { + auto &type = compiler.get(var->basetype); + auto &flags = + type.storage == StorageClassInput ? compiler.active_input_builtins : compiler.active_output_builtins; + flags |= 1ull << compiler.meta[id].decoration.builtin_type; + } + }; + + switch (opcode) + { + case OpStore: + if (length < 1) + return false; + + add_if_builtin(args[0]); + break; + + case OpCopyMemory: + if (length < 2) + return false; + + add_if_builtin(args[0]); + add_if_builtin(args[1]); + break; + + case OpCopyObject: + case OpLoad: + if (length < 3) + return false; + + add_if_builtin(args[2]); + break; + + case OpFunctionCall: + { + if (length < 3) + return false; + + uint32_t count = length - 3; + args += 3; + for (uint32_t i = 0; i < count; i++) + add_if_builtin(args[i]); + break; + } + + case OpAccessChain: + case OpInBoundsAccessChain: + { + if (length < 4) + return false; + + // Only consider global variables, cannot consider variables in functions yet, or other + // access chains as they have not been created yet. + auto *var = compiler.maybe_get(args[2]); + if (!var) + break; + + auto *type = &compiler.get(var->basetype); + + // Start traversing type hierarchy at the proper non-pointer types. + while (type->pointer) + { + assert(type->parent_type); + type = &compiler.get(type->parent_type); + } + + auto &flags = + type->storage == StorageClassInput ? compiler.active_input_builtins : compiler.active_output_builtins; + + uint32_t count = length - 3; + args += 3; + for (uint32_t i = 0; i < count; i++) + { + // Arrays + if (!type->array.empty()) + { + type = &compiler.get(type->parent_type); + } + // Structs + else if (type->basetype == SPIRType::Struct) + { + uint32_t index = compiler.get(args[i]).scalar(); + + if (index < uint32_t(compiler.meta[type->self].members.size())) + { + auto &decorations = compiler.meta[type->self].members[index]; + if (decorations.builtin) + flags |= 1ull << decorations.builtin_type; + } + + type = &compiler.get(type->member_types[index]); + } + else + { + // No point in traversing further. We won't find any extra builtins. + break; + } + } + break; + } + + default: + break; + } + + return true; +} + +void Compiler::update_active_builtins() +{ + active_input_builtins = 0; + active_output_builtins = 0; + ActiveBuiltinHandler handler(*this); + traverse_all_reachable_opcodes(get(entry_point), handler); +} diff --git a/libs/openFrameworks/vk/spirv-cross/include/spirv_cross.hpp b/libs/openFrameworks/vk/spirv-cross/include/spirv_cross.hpp new file mode 100644 index 00000000000..08b052c25fc --- /dev/null +++ b/libs/openFrameworks/vk/spirv-cross/include/spirv_cross.hpp @@ -0,0 +1,617 @@ +/* + * Copyright 2015-2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SPIRV_CROSS_HPP +#define SPIRV_CROSS_HPP + +#include "spirv.hpp" +#include "spirv_common.hpp" + +namespace spirv_cross +{ +class CFG; +struct Resource +{ + // Resources are identified with their SPIR-V ID. + // This is the ID of the OpVariable. + uint32_t id; + + // The type ID of the variable which includes arrays and all type modifications. + // This type ID is not suitable for parsing OpMemberDecoration of a struct and other decorations in general + // since these modifications typically happen on the base_type_id. + uint32_t type_id; + + // The base type of the declared resource. + // This type is the base type which ignores pointers and arrays of the type_id. + // This is mostly useful to parse decorations of the underlying type. + // base_type_id can also be obtained with get_type(get_type(type_id).self). + uint32_t base_type_id; + + // The declared name (OpName) of the resource. + // For Buffer blocks, the name actually reflects the externally + // visible Block name. + // + // This name can be retrieved again by using either + // get_name(id) or get_name(base_type_id) depending if it's a buffer block or not. + // + // This name can be an empty string in which case get_fallback_name(id) can be + // used which obtains a suitable fallback identifier for an ID. + std::string name; +}; + +struct ShaderResources +{ + std::vector uniform_buffers; + std::vector storage_buffers; + std::vector stage_inputs; + std::vector stage_outputs; + std::vector subpass_inputs; + std::vector storage_images; + std::vector sampled_images; + std::vector atomic_counters; + + // There can only be one push constant block, + // but keep the vector in case this restriction is lifted in the future. + std::vector push_constant_buffers; + + // For Vulkan GLSL and HLSL source, + // these correspond to separate texture2D and samplers respectively. + std::vector separate_images; + std::vector separate_samplers; +}; + +struct CombinedImageSampler +{ + // The ID of the sampler2D variable. + uint32_t combined_id; + // The ID of the texture2D variable. + uint32_t image_id; + // The ID of the sampler variable. + uint32_t sampler_id; +}; + +struct SpecializationConstant +{ + // The ID of the specialization constant. + uint32_t id; + // The constant ID of the constant, used in Vulkan during pipeline creation. + uint32_t constant_id; +}; + +struct BufferRange +{ + unsigned index; + size_t offset; + size_t range; +}; + +class Compiler +{ +public: + friend class CFG; + friend class DominatorBuilder; + + // The constructor takes a buffer of SPIR-V words and parses it. + Compiler(std::vector ir); + Compiler(const uint32_t *ir, size_t word_count); + + virtual ~Compiler() = default; + + // After parsing, API users can modify the SPIR-V via reflection and call this + // to disassemble the SPIR-V into the desired langauage. + // Sub-classes actually implement this. + virtual std::string compile(); + + // Gets the identifier (OpName) of an ID. If not defined, an empty string will be returned. + const std::string &get_name(uint32_t id) const; + + // Applies a decoration to an ID. Effectively injects OpDecorate. + void set_decoration(uint32_t id, spv::Decoration decoration, uint32_t argument = 0); + + // Overrides the identifier OpName of an ID. + // Identifiers beginning with underscores or identifiers which contain double underscores + // are reserved by the implementation. + void set_name(uint32_t id, const std::string &name); + + // Gets a bitmask for the decorations which are applied to ID. + // I.e. (1ull << spv::DecorationFoo) | (1ull << spv::DecorationBar) + uint64_t get_decoration_mask(uint32_t id) const; + + // Returns whether the decoration has been applied to the ID. + bool has_decoration(uint32_t id, spv::Decoration decoration) const; + + // Gets the value for decorations which take arguments. + // If the decoration is a boolean (i.e. spv::DecorationNonWritable), + // 1 will be returned. + // If decoration doesn't exist or decoration is not recognized, + // 0 will be returned. + uint32_t get_decoration(uint32_t id, spv::Decoration decoration) const; + + // Removes the decoration for a an ID. + void unset_decoration(uint32_t id, spv::Decoration decoration); + + // Gets the SPIR-V type associated with ID. + // Mostly used with Resource::type_id and Resource::base_type_id to parse the underlying type of a resource. + const SPIRType &get_type(uint32_t id) const; + + // Gets the SPIR-V type of a variable. + const SPIRType &get_type_from_variable(uint32_t id) const; + + // Gets the underlying storage class for an OpVariable. + spv::StorageClass get_storage_class(uint32_t id) const; + + // If get_name() is an empty string, get the fallback name which will be used + // instead in the disassembled source. + virtual const std::string get_fallback_name(uint32_t id) const + { + return join("_", id); + } + + // Given an OpTypeStruct in ID, obtain the identifier for member number "index". + // This may be an empty string. + const std::string &get_member_name(uint32_t id, uint32_t index) const; + + // Given an OpTypeStruct in ID, obtain the OpMemberDecoration for member number "index". + uint32_t get_member_decoration(uint32_t id, uint32_t index, spv::Decoration decoration) const; + + // Sets the member identifier for OpTypeStruct ID, member number "index". + void set_member_name(uint32_t id, uint32_t index, const std::string &name); + + // Sets the qualified member identifier for OpTypeStruct ID, member number "index". + void set_member_qualified_name(uint32_t id, uint32_t index, const std::string &name); + + // Gets the decoration mask for a member of a struct, similar to get_decoration_mask. + uint64_t get_member_decoration_mask(uint32_t id, uint32_t index) const; + + // Returns whether the decoration has been applied to a member of a struct. + bool has_member_decoration(uint32_t id, uint32_t index, spv::Decoration decoration) const; + + // Similar to set_decoration, but for struct members. + void set_member_decoration(uint32_t id, uint32_t index, spv::Decoration decoration, uint32_t argument = 0); + + // Unsets a member decoration, similar to unset_decoration. + void unset_member_decoration(uint32_t id, uint32_t index, spv::Decoration decoration); + + // Gets the fallback name for a member, similar to get_fallback_name. + virtual const std::string get_fallback_member_name(uint32_t index) const + { + return join("_", index); + } + + // Returns a vector of which members of a struct are potentially in use by a + // SPIR-V shader. The granularity of this analysis is per-member of a struct. + // This can be used for Buffer (UBO), BufferBlock (SSBO) and PushConstant blocks. + // ID is the Resource::id obtained from get_shader_resources(). + std::vector get_active_buffer_ranges(uint32_t id) const; + + // Returns the effective size of a buffer block. + size_t get_declared_struct_size(const SPIRType &struct_type) const; + + // Returns the effective size of a buffer block struct member. + virtual size_t get_declared_struct_member_size(const SPIRType &struct_type, uint32_t index) const; + + // Legacy GLSL compatibility method. Deprecated in favor of CompilerGLSL::flatten_buffer_block + SPIRV_CROSS_DEPRECATED("Please use flatten_buffer_block instead.") void flatten_interface_block(uint32_t id); + + // Returns a set of all global variables which are statically accessed + // by the control flow graph from the current entry point. + // Only variables which change the interface for a shader are returned, that is, + // variables with storage class of Input, Output, Uniform, UniformConstant, PushConstant and AtomicCounter + // storage classes are returned. + // + // To use the returned set as the filter for which variables are used during compilation, + // this set can be moved to set_enabled_interface_variables(). + std::unordered_set get_active_interface_variables() const; + + // Sets the interface variables which are used during compilation. + // By default, all variables are used. + // Once set, compile() will only consider the set in active_variables. + void set_enabled_interface_variables(std::unordered_set active_variables); + + // Query shader resources, use ids with reflection interface to modify or query binding points, etc. + ShaderResources get_shader_resources() const; + + // Query shader resources, but only return the variables which are part of active_variables. + // E.g.: get_shader_resources(get_active_variables()) to only return the variables which are statically + // accessed. + ShaderResources get_shader_resources(const std::unordered_set &active_variables) const; + + // Remapped variables are considered built-in variables and a backend will + // not emit a declaration for this variable. + // This is mostly useful for making use of builtins which are dependent on extensions. + void set_remapped_variable_state(uint32_t id, bool remap_enable); + bool get_remapped_variable_state(uint32_t id) const; + + // For subpassInput variables which are remapped to plain variables, + // the number of components in the remapped + // variable must be specified as the backing type of subpass inputs are opaque. + void set_subpass_input_remapped_components(uint32_t id, uint32_t components); + uint32_t get_subpass_input_remapped_components(uint32_t id) const; + + // All operations work on the current entry point. + // Entry points can be swapped out with set_entry_point(). + // Entry points should be set right after the constructor completes as some reflection functions traverse the graph from the entry point. + // Resource reflection also depends on the entry point. + // By default, the current entry point is set to the first OpEntryPoint which appears in the SPIR-V module. + std::vector get_entry_points() const; + void set_entry_point(const std::string &name); + + // Returns the internal data structure for entry points to allow poking around. + const SPIREntryPoint &get_entry_point(const std::string &name) const; + SPIREntryPoint &get_entry_point(const std::string &name); + + // Query and modify OpExecutionMode. + uint64_t get_execution_mode_mask() const; + void unset_execution_mode(spv::ExecutionMode mode); + void set_execution_mode(spv::ExecutionMode mode, uint32_t arg0 = 0, uint32_t arg1 = 0, uint32_t arg2 = 0); + + // Gets argument for an execution mode (LocalSize, Invocations, OutputVertices). + // For LocalSize, the index argument is used to select the dimension (X = 0, Y = 1, Z = 2). + // For execution modes which do not have arguments, 0 is returned. + uint32_t get_execution_mode_argument(spv::ExecutionMode mode, uint32_t index = 0) const; + spv::ExecutionModel get_execution_model() const; + + // Analyzes all separate image and samplers used from the currently selected entry point, + // and re-routes them all to a combined image sampler instead. + // This is required to "support" separate image samplers in targets which do not natively support + // this feature, like GLSL/ESSL. + // + // This must be called before compile() if such remapping is desired. + // This call will add new sampled images to the SPIR-V, + // so it will appear in reflection if get_shader_resources() is called after build_combined_image_samplers. + // + // If any image/sampler remapping was found, no separate image/samplers will appear in the decompiled output, + // but will still appear in reflection. + // + // The resulting samplers will be void of any decorations like name, descriptor sets and binding points, + // so this can be added before compile() if desired. + // + // Combined image samplers originating from this set are always considered active variables. + void build_combined_image_samplers(); + + // Gets a remapping for the combined image samplers. + const std::vector &get_combined_image_samplers() const + { + return combined_image_samplers; + } + + // Set a new variable type remap callback. + // The type remapping is designed to allow global interface variable to assume more special types. + // A typical example here is to remap sampler2D into samplerExternalOES, which currently isn't supported + // directly by SPIR-V. + // + // In compile() while emitting code, + // for every variable that is declared, including function parameters, the callback will be called + // and the API user has a chance to change the textual representation of the type used to declare the variable. + // The API user can detect special patterns in names to guide the remapping. + void set_variable_type_remap_callback(VariableTypeRemapCallback cb) + { + variable_remap_callback = std::move(cb); + } + + // API for querying which specialization constants exist. + // To modify a specialization constant before compile(), use get_constant(constant.id), + // then update constants directly in the SPIRConstant data structure. + // For composite types, the subconstants can be iterated over and modified. + // constant_type is the SPIRType for the specialization constant, + // which can be queried to determine which fields in the unions should be poked at. + std::vector get_specialization_constants() const; + SPIRConstant &get_constant(uint32_t id); + const SPIRConstant &get_constant(uint32_t id) const; + + uint32_t get_current_id_bound() const + { + return uint32_t(ids.size()); + } + + // API for querying buffer objects. + // The type passed in here should be the base type of a resource, i.e. + // get_type(resource.base_type_id) + // as decorations are set in the basic Block type. + // The type passed in here must have these decorations set, or an exception is raised. + // Only UBOs and SSBOs or sub-structs which are part of these buffer types will have these decorations set. + uint32_t type_struct_member_offset(const SPIRType &type, uint32_t index) const; + uint32_t type_struct_member_array_stride(const SPIRType &type, uint32_t index) const; + uint32_t type_struct_member_matrix_stride(const SPIRType &type, uint32_t index) const; + +protected: + const uint32_t *stream(const Instruction &instr) const + { + // If we're not going to use any arguments, just return nullptr. + // We want to avoid case where we return an out of range pointer + // that trips debug assertions on some platforms. + if (!instr.length) + return nullptr; + + if (instr.offset + instr.length > spirv.size()) + SPIRV_CROSS_THROW("Compiler::stream() out of range."); + return &spirv[instr.offset]; + } + std::vector spirv; + + std::vector inst; + std::vector ids; + std::vector meta; + + SPIRFunction *current_function = nullptr; + SPIRBlock *current_block = nullptr; + std::vector global_variables; + std::vector aliased_variables; + std::unordered_set active_interface_variables; + bool check_active_interface_variables = false; + + // If our IDs are out of range here as part of opcodes, throw instead of + // undefined behavior. + template + T &set(uint32_t id, P &&... args) + { + auto &var = variant_set(ids.at(id), std::forward

(args)...); + var.self = id; + return var; + } + + template + T &get(uint32_t id) + { + return variant_get(ids.at(id)); + } + + template + T *maybe_get(uint32_t id) + { + if (ids.at(id).get_type() == T::type) + return &get(id); + else + return nullptr; + } + + template + const T &get(uint32_t id) const + { + return variant_get(ids.at(id)); + } + + template + const T *maybe_get(uint32_t id) const + { + if (ids.at(id).get_type() == T::type) + return &get(id); + else + return nullptr; + } + + uint32_t entry_point = 0; + // Normally, we'd stick SPIREntryPoint in ids array, but it conflicts with SPIRFunction. + // Entry points can therefore be seen as some sort of meta structure. + std::unordered_map entry_points; + const SPIREntryPoint &get_entry_point() const; + SPIREntryPoint &get_entry_point(); + + struct Source + { + uint32_t version = 0; + bool es = false; + bool known = false; + + Source() = default; + } source; + + std::unordered_set loop_blocks; + std::unordered_set continue_blocks; + std::unordered_set loop_merge_targets; + std::unordered_set selection_merge_targets; + std::unordered_set multiselect_merge_targets; + + virtual std::string to_name(uint32_t id, bool allow_alias = true) const; + bool is_builtin_variable(const SPIRVariable &var) const; + bool is_hidden_variable(const SPIRVariable &var, bool include_builtins = false) const; + bool is_immutable(uint32_t id) const; + bool is_member_builtin(const SPIRType &type, uint32_t index, spv::BuiltIn *builtin) const; + bool is_scalar(const SPIRType &type) const; + bool is_vector(const SPIRType &type) const; + bool is_matrix(const SPIRType &type) const; + const SPIRType &expression_type(uint32_t id) const; + bool expression_is_lvalue(uint32_t id) const; + bool variable_storage_is_aliased(const SPIRVariable &var); + SPIRVariable *maybe_get_backing_variable(uint32_t chain); + + void register_read(uint32_t expr, uint32_t chain, bool forwarded); + void register_write(uint32_t chain); + + inline bool is_continue(uint32_t next) const + { + return continue_blocks.find(next) != end(continue_blocks); + } + + inline bool is_break(uint32_t next) const + { + return loop_merge_targets.find(next) != end(loop_merge_targets) || + multiselect_merge_targets.find(next) != end(multiselect_merge_targets); + } + + inline bool is_conditional(uint32_t next) const + { + return selection_merge_targets.find(next) != end(selection_merge_targets) && + multiselect_merge_targets.find(next) == end(multiselect_merge_targets); + } + + // Dependency tracking for temporaries read from variables. + void flush_dependees(SPIRVariable &var); + void flush_all_active_variables(); + void flush_all_atomic_capable_variables(); + void flush_all_aliased_variables(); + void register_global_read_dependencies(const SPIRBlock &func, uint32_t id); + void register_global_read_dependencies(const SPIRFunction &func, uint32_t id); + std::unordered_set invalid_expressions; + + void update_name_cache(std::unordered_set &cache, std::string &name); + + bool function_is_pure(const SPIRFunction &func); + bool block_is_pure(const SPIRBlock &block); + bool block_is_outside_flow_control_from_block(const SPIRBlock &from, const SPIRBlock &to); + + bool execution_is_branchless(const SPIRBlock &from, const SPIRBlock &to) const; + bool execution_is_noop(const SPIRBlock &from, const SPIRBlock &to) const; + SPIRBlock::ContinueBlockType continue_block_type(const SPIRBlock &continue_block) const; + + bool force_recompile = false; + + bool block_is_loop_candidate(const SPIRBlock &block, SPIRBlock::Method method) const; + + uint32_t increase_bound_by(uint32_t incr_amount); + + bool types_are_logically_equivalent(const SPIRType &a, const SPIRType &b) const; + void inherit_expression_dependencies(uint32_t dst, uint32_t source); + + // For proper multiple entry point support, allow querying if an Input or Output + // variable is part of that entry points interface. + bool interface_variable_exists_in_entry_point(uint32_t id) const; + + std::vector combined_image_samplers; + + void remap_variable_type_name(const SPIRType &type, const std::string &var_name, std::string &type_name) const + { + if (variable_remap_callback) + variable_remap_callback(type, var_name, type_name); + } + + void analyze_variable_scope(SPIRFunction &function); + +protected: + void parse(); + void parse(const Instruction &i); + + // Used internally to implement various traversals for queries. + struct OpcodeHandler + { + virtual ~OpcodeHandler() = default; + + // Return true if traversal should continue. + // If false, traversal will end immediately. + virtual bool handle(spv::Op opcode, const uint32_t *args, uint32_t length) = 0; + + virtual bool follow_function_call(const SPIRFunction &) + { + return true; + } + + virtual void set_current_block(const SPIRBlock &) + { + } + + virtual bool begin_function_scope(const uint32_t *, uint32_t) + { + return true; + } + + virtual bool end_function_scope(const uint32_t *, uint32_t) + { + return true; + } + }; + + struct BufferAccessHandler : OpcodeHandler + { + BufferAccessHandler(const Compiler &compiler_, std::vector &ranges_, uint32_t id_) + : compiler(compiler_) + , ranges(ranges_) + , id(id_) + { + } + + bool handle(spv::Op opcode, const uint32_t *args, uint32_t length) override; + + const Compiler &compiler; + std::vector &ranges; + uint32_t id; + + std::unordered_set seen; + }; + + struct InterfaceVariableAccessHandler : OpcodeHandler + { + InterfaceVariableAccessHandler(const Compiler &compiler_, std::unordered_set &variables_) + : compiler(compiler_) + , variables(variables_) + { + } + + bool handle(spv::Op opcode, const uint32_t *args, uint32_t length) override; + + const Compiler &compiler; + std::unordered_set &variables; + }; + + struct CombinedImageSamplerHandler : OpcodeHandler + { + CombinedImageSamplerHandler(Compiler &compiler_) + : compiler(compiler_) + { + } + bool handle(spv::Op opcode, const uint32_t *args, uint32_t length) override; + bool begin_function_scope(const uint32_t *args, uint32_t length) override; + bool end_function_scope(const uint32_t *args, uint32_t length) override; + + Compiler &compiler; + + // Each function in the call stack needs its own remapping for parameters so we can deduce which global variable each texture/sampler the parameter is statically bound to. + std::stack> parameter_remapping; + std::stack functions; + + uint32_t remap_parameter(uint32_t id); + void push_remap_parameters(const SPIRFunction &func, const uint32_t *args, uint32_t length); + void pop_remap_parameters(); + void register_combined_image_sampler(SPIRFunction &caller, uint32_t texture_id, uint32_t sampler_id); + }; + + struct ActiveBuiltinHandler : OpcodeHandler + { + ActiveBuiltinHandler(Compiler &compiler_) + : compiler(compiler_) + { + } + + bool handle(spv::Op opcode, const uint32_t *args, uint32_t length) override; + Compiler &compiler; + }; + + bool traverse_all_reachable_opcodes(const SPIRBlock &block, OpcodeHandler &handler) const; + bool traverse_all_reachable_opcodes(const SPIRFunction &block, OpcodeHandler &handler) const; + // This must be an ordered data structure so we always pick the same type aliases. + std::vector global_struct_cache; + + ShaderResources get_shader_resources(const std::unordered_set *active_variables) const; + + VariableTypeRemapCallback variable_remap_callback; + + uint64_t get_buffer_block_flags(const SPIRVariable &var); + bool get_common_basic_type(const SPIRType &type, SPIRType::BaseType &base_type); + + std::unordered_set forced_temporaries; + std::unordered_set forwarded_temporaries; + + uint64_t active_input_builtins = 0; + uint64_t active_output_builtins = 0; + // Traverses all reachable opcodes and sets active_builtins to a bitmask of all builtin variables which are accessed in the shader. + void update_active_builtins(); + + void analyze_parameter_preservation( + SPIRFunction &entry, const CFG &cfg, + const std::unordered_map> &variable_to_blocks); +}; +} + +#endif diff --git a/libs/openFrameworks/vk/spirv-cross/license/LICENSE b/libs/openFrameworks/vk/spirv-cross/license/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/libs/openFrameworks/vk/spirv-cross/license/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/libs/openFrameworks/vk/spooky/SpookyV2.cpp b/libs/openFrameworks/vk/spooky/SpookyV2.cpp new file mode 100644 index 00000000000..735bd56296e --- /dev/null +++ b/libs/openFrameworks/vk/spooky/SpookyV2.cpp @@ -0,0 +1,351 @@ +// Spooky Hash +// A 128-bit noncryptographic hash, for checksums and table lookup +// By Bob Jenkins. Public domain. +// Oct 31 2010: published framework, disclaimer ShortHash isn't right +// Nov 7 2010: disabled ShortHash +// Oct 31 2011: replace End, ShortMix, ShortEnd, enable ShortHash again +// April 10 2012: buffer overflow on platforms without unaligned reads +// July 12 2012: was passing out variables in final to in/out in short +// July 30 2012: I reintroduced the buffer overflow +// August 5 2012: SpookyV2: d = should be d += in short hash, and remove extra mix from long hash + +#include +#include "SpookyV2.h" + +#define ALLOW_UNALIGNED_READS 1 + +// +// short hash ... it could be used on any message, +// but it's used by Spooky just for short messages. +// +void SpookyHash::Short( + const void *message, + size_t length, + uint64 *hash1, + uint64 *hash2) +{ + uint64 buf[2*sc_numVars]; + union + { + const uint8 *p8; + uint32 *p32; + uint64 *p64; + size_t i; + } u; + + u.p8 = (const uint8 *)message; + + if (!ALLOW_UNALIGNED_READS && (u.i & 0x7)) + { + memcpy(buf, message, length); + u.p64 = buf; + } + + size_t remainder = length%32; + uint64 a=*hash1; + uint64 b=*hash2; + uint64 c=sc_const; + uint64 d=sc_const; + + if (length > 15) + { + const uint64 *end = u.p64 + (length/32)*4; + + // handle all complete sets of 32 bytes + for (; u.p64 < end; u.p64 += 4) + { + c += u.p64[0]; + d += u.p64[1]; + ShortMix(a,b,c,d); + a += u.p64[2]; + b += u.p64[3]; + } + + //Handle the case of 16+ remaining bytes. + if (remainder >= 16) + { + c += u.p64[0]; + d += u.p64[1]; + ShortMix(a,b,c,d); + u.p64 += 2; + remainder -= 16; + } + } + + // Handle the last 0..15 bytes, and its length + d += ((uint64)length) << 56; + switch (remainder) + { + case 15: + d += ((uint64)u.p8[14]) << 48; + case 14: + d += ((uint64)u.p8[13]) << 40; + case 13: + d += ((uint64)u.p8[12]) << 32; + case 12: + d += u.p32[2]; + c += u.p64[0]; + break; + case 11: + d += ((uint64)u.p8[10]) << 16; + case 10: + d += ((uint64)u.p8[9]) << 8; + case 9: + d += (uint64)u.p8[8]; + case 8: + c += u.p64[0]; + break; + case 7: + c += ((uint64)u.p8[6]) << 48; + case 6: + c += ((uint64)u.p8[5]) << 40; + case 5: + c += ((uint64)u.p8[4]) << 32; + case 4: + c += u.p32[0]; + break; + case 3: + c += ((uint64)u.p8[2]) << 16; + case 2: + c += ((uint64)u.p8[1]) << 8; + case 1: + c += (uint64)u.p8[0]; + break; + case 0: + c += sc_const; + d += sc_const; + } + ShortEnd(a,b,c,d); + *hash1 = a; + *hash2 = b; +} + + + + +// do the whole hash in one call +void SpookyHash::Hash128( + const void *message, + size_t length, + uint64 *hash1, + uint64 *hash2) +{ + if (length < sc_bufSize) + { + Short(message, length, hash1, hash2); + return; + } + + uint64 h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11; + uint64 buf[sc_numVars]; + uint64 *end; + union + { + const uint8 *p8; + uint64 *p64; + size_t i; + } u; + size_t remainder; + + h0=h3=h6=h9 = *hash1; + h1=h4=h7=h10 = *hash2; + h2=h5=h8=h11 = sc_const; + + u.p8 = (const uint8 *)message; + end = u.p64 + (length/sc_blockSize)*sc_numVars; + + // handle all whole sc_blockSize blocks of bytes + if (ALLOW_UNALIGNED_READS || ((u.i & 0x7) == 0)) + { + while (u.p64 < end) + { + Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11); + u.p64 += sc_numVars; + } + } + else + { + while (u.p64 < end) + { + memcpy(buf, u.p64, sc_blockSize); + Mix(buf, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11); + u.p64 += sc_numVars; + } + } + + // handle the last partial block of sc_blockSize bytes + remainder = (length - ((const uint8 *)end-(const uint8 *)message)); + memcpy(buf, end, remainder); + memset(((uint8 *)buf)+remainder, 0, sc_blockSize-remainder); + ((uint8 *)buf)[sc_blockSize-1] = remainder; + + // do some final mixing + End(buf, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11); + *hash1 = h0; + *hash2 = h1; +} + + + +// init spooky state +void SpookyHash::Init(uint64 seed1, uint64 seed2) +{ + m_length = 0; + m_remainder = 0; + m_state[0] = seed1; + m_state[1] = seed2; +} + + +// add a message fragment to the state +void SpookyHash::Update(const void *message, size_t length) +{ + uint64 h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11; + size_t newLength = length + m_remainder; + uint8 remainder; + union + { + const uint8 *p8; + uint64 *p64; + size_t i; + } u; + const uint64 *end; + + // Is this message fragment too short? If it is, stuff it away. + if (newLength < sc_bufSize) + { + memcpy(&((uint8 *)m_data)[m_remainder], message, length); + m_length = length + m_length; + m_remainder = (uint8)newLength; + return; + } + + // init the variables + if (m_length < sc_bufSize) + { + h0=h3=h6=h9 = m_state[0]; + h1=h4=h7=h10 = m_state[1]; + h2=h5=h8=h11 = sc_const; + } + else + { + h0 = m_state[0]; + h1 = m_state[1]; + h2 = m_state[2]; + h3 = m_state[3]; + h4 = m_state[4]; + h5 = m_state[5]; + h6 = m_state[6]; + h7 = m_state[7]; + h8 = m_state[8]; + h9 = m_state[9]; + h10 = m_state[10]; + h11 = m_state[11]; + } + m_length = length + m_length; + + // if we've got anything stuffed away, use it now + if (m_remainder) + { + uint8 prefix = sc_bufSize-m_remainder; + memcpy(&(((uint8 *)m_data)[m_remainder]), message, prefix); + u.p64 = m_data; + Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11); + Mix(&u.p64[sc_numVars], h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11); + u.p8 = ((const uint8 *)message) + prefix; + length -= prefix; + } + else + { + u.p8 = (const uint8 *)message; + } + + // handle all whole blocks of sc_blockSize bytes + end = u.p64 + (length/sc_blockSize)*sc_numVars; + remainder = (uint8)(length-((const uint8 *)end-u.p8)); + if (ALLOW_UNALIGNED_READS || (u.i & 0x7) == 0) + { + while (u.p64 < end) + { + Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11); + u.p64 += sc_numVars; + } + } + else + { + while (u.p64 < end) + { + memcpy(m_data, u.p8, sc_blockSize); + Mix(m_data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11); + u.p64 += sc_numVars; + } + } + + // stuff away the last few bytes + m_remainder = remainder; + memcpy(m_data, end, remainder); + + // stuff away the variables + m_state[0] = h0; + m_state[1] = h1; + m_state[2] = h2; + m_state[3] = h3; + m_state[4] = h4; + m_state[5] = h5; + m_state[6] = h6; + m_state[7] = h7; + m_state[8] = h8; + m_state[9] = h9; + m_state[10] = h10; + m_state[11] = h11; +} + + +// report the hash for the concatenation of all message fragments so far +void SpookyHash::Final(uint64 *hash1, uint64 *hash2) +{ + // init the variables + if (m_length < sc_bufSize) + { + *hash1 = m_state[0]; + *hash2 = m_state[1]; + Short( m_data, m_length, hash1, hash2); + return; + } + + const uint64 *data = (const uint64 *)m_data; + uint8 remainder = m_remainder; + + uint64 h0 = m_state[0]; + uint64 h1 = m_state[1]; + uint64 h2 = m_state[2]; + uint64 h3 = m_state[3]; + uint64 h4 = m_state[4]; + uint64 h5 = m_state[5]; + uint64 h6 = m_state[6]; + uint64 h7 = m_state[7]; + uint64 h8 = m_state[8]; + uint64 h9 = m_state[9]; + uint64 h10 = m_state[10]; + uint64 h11 = m_state[11]; + + if (remainder >= sc_blockSize) + { + // m_data can contain two blocks; handle any whole first block + Mix(data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11); + data += sc_numVars; + remainder -= sc_blockSize; + } + + // mix in the last partial block, and the length mod sc_blockSize + memset(&((uint8 *)data)[remainder], 0, (sc_blockSize-remainder)); + + ((uint8 *)data)[sc_blockSize-1] = remainder; + + // do some final mixing + End(data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11); + + *hash1 = h0; + *hash2 = h1; +} + diff --git a/libs/openFrameworks/vk/spooky/SpookyV2.h b/libs/openFrameworks/vk/spooky/SpookyV2.h new file mode 100644 index 00000000000..4ccc0d523aa --- /dev/null +++ b/libs/openFrameworks/vk/spooky/SpookyV2.h @@ -0,0 +1,299 @@ +// +// SpookyHash: a 128-bit noncryptographic hash function +// By Bob Jenkins, public domain +// Oct 31 2010: alpha, framework + SpookyHash::Mix appears right +// Oct 31 2011: alpha again, Mix only good to 2^^69 but rest appears right +// Dec 31 2011: beta, improved Mix, tested it for 2-bit deltas +// Feb 2 2012: production, same bits as beta +// Feb 5 2012: adjusted definitions of uint* to be more portable +// Mar 30 2012: 3 bytes/cycle, not 4. Alpha was 4 but wasn't thorough enough. +// August 5 2012: SpookyV2 (different results) +// +// Up to 3 bytes/cycle for long messages. Reasonably fast for short messages. +// All 1 or 2 bit deltas achieve avalanche within 1% bias per output bit. +// +// This was developed for and tested on 64-bit x86-compatible processors. +// It assumes the processor is little-endian. There is a macro +// controlling whether unaligned reads are allowed (by default they are). +// This should be an equally good hash on big-endian machines, but it will +// compute different results on them than on little-endian machines. +// +// Google's CityHash has similar specs to SpookyHash, and CityHash is faster +// on new Intel boxes. MD4 and MD5 also have similar specs, but they are orders +// of magnitude slower. CRCs are two or more times slower, but unlike +// SpookyHash, they have nice math for combining the CRCs of pieces to form +// the CRCs of wholes. There are also cryptographic hashes, but those are even +// slower than MD5. +// + +#include + +#ifdef _MSC_VER +# define INLINE __forceinline + typedef unsigned __int64 uint64; + typedef unsigned __int32 uint32; + typedef unsigned __int16 uint16; + typedef unsigned __int8 uint8; +#else +# include +# define INLINE inline + typedef uint64_t uint64; + typedef uint32_t uint32; + typedef uint16_t uint16; + typedef uint8_t uint8; +#endif + + +class SpookyHash +{ +public: + // + // SpookyHash: hash a single message in one call, produce 128-bit output + // + static void Hash128( + const void *message, // message to hash + size_t length, // length of message in bytes + uint64 *hash1, // in/out: in seed 1, out hash value 1 + uint64 *hash2); // in/out: in seed 2, out hash value 2 + + // + // Hash64: hash a single message in one call, return 64-bit output + // + static uint64 Hash64( + const void *message, // message to hash + size_t length, // length of message in bytes + uint64 seed) // seed + { + uint64 hash1 = seed; + Hash128(message, length, &hash1, &seed); + return hash1; + } + + // + // Hash32: hash a single message in one call, produce 32-bit output + // + static uint32 Hash32( + const void *message, // message to hash + size_t length, // length of message in bytes + uint32 seed) // seed + { + uint64 hash1 = seed, hash2 = seed; + Hash128(message, length, &hash1, &hash2); + return (uint32)hash1; + } + + // + // Init: initialize the context of a SpookyHash + // + void Init( + uint64 seed1, // any 64-bit value will do, including 0 + uint64 seed2); // different seeds produce independent hashes + + // + // Update: add a piece of a message to a SpookyHash state + // + void Update( + const void *message, // message fragment + size_t length); // length of message fragment in bytes + + + // + // Final: compute the hash for the current SpookyHash state + // + // This does not modify the state; you can keep updating it afterward + // + // The result is the same as if SpookyHash() had been called with + // all the pieces concatenated into one message. + // + void Final( + uint64 *hash1, // out only: first 64 bits of hash value. + uint64 *hash2); // out only: second 64 bits of hash value. + + // + // left rotate a 64-bit value by k bytes + // + static INLINE uint64 Rot64(uint64 x, int k) + { + return (x << k) | (x >> (64 - k)); + } + + // + // This is used if the input is 96 bytes long or longer. + // + // The internal state is fully overwritten every 96 bytes. + // Every input bit appears to cause at least 128 bits of entropy + // before 96 other bytes are combined, when run forward or backward + // For every input bit, + // Two inputs differing in just that input bit + // Where "differ" means xor or subtraction + // And the base value is random + // When run forward or backwards one Mix + // I tried 3 pairs of each; they all differed by at least 212 bits. + // + static INLINE void Mix( + const uint64 *data, + uint64 &s0, uint64 &s1, uint64 &s2, uint64 &s3, + uint64 &s4, uint64 &s5, uint64 &s6, uint64 &s7, + uint64 &s8, uint64 &s9, uint64 &s10,uint64 &s11) + { + s0 += data[0]; s2 ^= s10; s11 ^= s0; s0 = Rot64(s0,11); s11 += s1; + s1 += data[1]; s3 ^= s11; s0 ^= s1; s1 = Rot64(s1,32); s0 += s2; + s2 += data[2]; s4 ^= s0; s1 ^= s2; s2 = Rot64(s2,43); s1 += s3; + s3 += data[3]; s5 ^= s1; s2 ^= s3; s3 = Rot64(s3,31); s2 += s4; + s4 += data[4]; s6 ^= s2; s3 ^= s4; s4 = Rot64(s4,17); s3 += s5; + s5 += data[5]; s7 ^= s3; s4 ^= s5; s5 = Rot64(s5,28); s4 += s6; + s6 += data[6]; s8 ^= s4; s5 ^= s6; s6 = Rot64(s6,39); s5 += s7; + s7 += data[7]; s9 ^= s5; s6 ^= s7; s7 = Rot64(s7,57); s6 += s8; + s8 += data[8]; s10 ^= s6; s7 ^= s8; s8 = Rot64(s8,55); s7 += s9; + s9 += data[9]; s11 ^= s7; s8 ^= s9; s9 = Rot64(s9,54); s8 += s10; + s10 += data[10]; s0 ^= s8; s9 ^= s10; s10 = Rot64(s10,22); s9 += s11; + s11 += data[11]; s1 ^= s9; s10 ^= s11; s11 = Rot64(s11,46); s10 += s0; + } + + // + // Mix all 12 inputs together so that h0, h1 are a hash of them all. + // + // For two inputs differing in just the input bits + // Where "differ" means xor or subtraction + // And the base value is random, or a counting value starting at that bit + // The final result will have each bit of h0, h1 flip + // For every input bit, + // with probability 50 +- .3% + // For every pair of input bits, + // with probability 50 +- 3% + // + // This does not rely on the last Mix() call having already mixed some. + // Two iterations was almost good enough for a 64-bit result, but a + // 128-bit result is reported, so End() does three iterations. + // + static INLINE void EndPartial( + uint64 &h0, uint64 &h1, uint64 &h2, uint64 &h3, + uint64 &h4, uint64 &h5, uint64 &h6, uint64 &h7, + uint64 &h8, uint64 &h9, uint64 &h10,uint64 &h11) + { + h11+= h1; h2 ^= h11; h1 = Rot64(h1,44); + h0 += h2; h3 ^= h0; h2 = Rot64(h2,15); + h1 += h3; h4 ^= h1; h3 = Rot64(h3,34); + h2 += h4; h5 ^= h2; h4 = Rot64(h4,21); + h3 += h5; h6 ^= h3; h5 = Rot64(h5,38); + h4 += h6; h7 ^= h4; h6 = Rot64(h6,33); + h5 += h7; h8 ^= h5; h7 = Rot64(h7,10); + h6 += h8; h9 ^= h6; h8 = Rot64(h8,13); + h7 += h9; h10^= h7; h9 = Rot64(h9,38); + h8 += h10; h11^= h8; h10= Rot64(h10,53); + h9 += h11; h0 ^= h9; h11= Rot64(h11,42); + h10+= h0; h1 ^= h10; h0 = Rot64(h0,54); + } + + static INLINE void End( + const uint64 *data, + uint64 &h0, uint64 &h1, uint64 &h2, uint64 &h3, + uint64 &h4, uint64 &h5, uint64 &h6, uint64 &h7, + uint64 &h8, uint64 &h9, uint64 &h10,uint64 &h11) + { + h0 += data[0]; h1 += data[1]; h2 += data[2]; h3 += data[3]; + h4 += data[4]; h5 += data[5]; h6 += data[6]; h7 += data[7]; + h8 += data[8]; h9 += data[9]; h10 += data[10]; h11 += data[11]; + EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11); + EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11); + EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11); + } + + // + // The goal is for each bit of the input to expand into 128 bits of + // apparent entropy before it is fully overwritten. + // n trials both set and cleared at least m bits of h0 h1 h2 h3 + // n: 2 m: 29 + // n: 3 m: 46 + // n: 4 m: 57 + // n: 5 m: 107 + // n: 6 m: 146 + // n: 7 m: 152 + // when run forwards or backwards + // for all 1-bit and 2-bit diffs + // with diffs defined by either xor or subtraction + // with a base of all zeros plus a counter, or plus another bit, or random + // + static INLINE void ShortMix(uint64 &h0, uint64 &h1, uint64 &h2, uint64 &h3) + { + h2 = Rot64(h2,50); h2 += h3; h0 ^= h2; + h3 = Rot64(h3,52); h3 += h0; h1 ^= h3; + h0 = Rot64(h0,30); h0 += h1; h2 ^= h0; + h1 = Rot64(h1,41); h1 += h2; h3 ^= h1; + h2 = Rot64(h2,54); h2 += h3; h0 ^= h2; + h3 = Rot64(h3,48); h3 += h0; h1 ^= h3; + h0 = Rot64(h0,38); h0 += h1; h2 ^= h0; + h1 = Rot64(h1,37); h1 += h2; h3 ^= h1; + h2 = Rot64(h2,62); h2 += h3; h0 ^= h2; + h3 = Rot64(h3,34); h3 += h0; h1 ^= h3; + h0 = Rot64(h0,5); h0 += h1; h2 ^= h0; + h1 = Rot64(h1,36); h1 += h2; h3 ^= h1; + } + + // + // Mix all 4 inputs together so that h0, h1 are a hash of them all. + // + // For two inputs differing in just the input bits + // Where "differ" means xor or subtraction + // And the base value is random, or a counting value starting at that bit + // The final result will have each bit of h0, h1 flip + // For every input bit, + // with probability 50 +- .3% (it is probably better than that) + // For every pair of input bits, + // with probability 50 +- .75% (the worst case is approximately that) + // + static INLINE void ShortEnd(uint64 &h0, uint64 &h1, uint64 &h2, uint64 &h3) + { + h3 ^= h2; h2 = Rot64(h2,15); h3 += h2; + h0 ^= h3; h3 = Rot64(h3,52); h0 += h3; + h1 ^= h0; h0 = Rot64(h0,26); h1 += h0; + h2 ^= h1; h1 = Rot64(h1,51); h2 += h1; + h3 ^= h2; h2 = Rot64(h2,28); h3 += h2; + h0 ^= h3; h3 = Rot64(h3,9); h0 += h3; + h1 ^= h0; h0 = Rot64(h0,47); h1 += h0; + h2 ^= h1; h1 = Rot64(h1,54); h2 += h1; + h3 ^= h2; h2 = Rot64(h2,32); h3 += h2; + h0 ^= h3; h3 = Rot64(h3,25); h0 += h3; + h1 ^= h0; h0 = Rot64(h0,63); h1 += h0; + } + +private: + + // + // Short is used for messages under 192 bytes in length + // Short has a low startup cost, the normal mode is good for long + // keys, the cost crossover is at about 192 bytes. The two modes were + // held to the same quality bar. + // + static void Short( + const void *message, // message (array of bytes, not necessarily aligned) + size_t length, // length of message (in bytes) + uint64 *hash1, // in/out: in the seed, out the hash value + uint64 *hash2); // in/out: in the seed, out the hash value + + // number of uint64's in internal state + static const size_t sc_numVars = 12; + + // size of the internal state + static const size_t sc_blockSize = sc_numVars*8; + + // size of buffer of unhashed data, in bytes + static const size_t sc_bufSize = 2*sc_blockSize; + + // + // sc_const: a constant which: + // * is not zero + // * is odd + // * is a not-very-regular mix of 1's and 0's + // * does not need any other special mathematical properties + // + static const uint64 sc_const = 0xdeadbeefdeadbeefLL; + + uint64 m_data[2*sc_numVars]; // unhashed data, for partial messages + uint64 m_state[sc_numVars]; // internal state of the hash + size_t m_length; // total length of the input so far + uint8 m_remainder; // length of unhashed data stashed in m_data +}; + + + diff --git a/libs/openFrameworksCompiled/project/makefileCommon/config.linux.common.mk b/libs/openFrameworksCompiled/project/makefileCommon/config.linux.common.mk index 5d56295cdf7..f2c0a64eb23 100644 --- a/libs/openFrameworksCompiled/project/makefileCommon/config.linux.common.mk +++ b/libs/openFrameworksCompiled/project/makefileCommon/config.linux.common.mk @@ -215,7 +215,7 @@ endif # generation. # # Each item in the PLATFORM_CORE_EXCLUSIONS list will be treated as a complete -# string unless teh user adds a wildcard (%) operator to match subdirectories. +# string unless the user adds a wildcard (%) operator to match subdirectories. # GNU make only allows one wildcard for matching. The second wildcard (%) is # treated literally. # @@ -249,6 +249,7 @@ PLATFORM_CORE_EXCLUSIONS += $(OF_LIBS_PATH)/assimp/% PLATFORM_CORE_EXCLUSIONS += $(OF_LIBS_PATH)/rtAudio/% PLATFORM_CORE_EXCLUSIONS += $(OF_LIBS_PATH)/openssl/% PLATFORM_CORE_EXCLUSIONS += $(OF_LIBS_PATH)/boost/% +# PLATFORM_CORE_EXCLUSIONS += $(OF_LIBS_PATH)/shaderc/% PLATFORM_CORE_EXCLUSIONS += $(OF_LIBS_PATH)/glfw/% PLATFORM_CORE_EXCLUSIONS += $(OF_LIBS_PATH)/curl/% PLATFORM_CORE_EXCLUSIONS += $(OF_LIBS_PATH)/uriparser/% @@ -270,6 +271,7 @@ endif ################################################################################ PLATFORM_HEADER_SEARCH_PATHS = +PLATFORM_HEADER_SEARCH_PATHS += "$(VULKAN_SDK)/include" ################################################################################ # PLATFORM LIBRARIES @@ -300,13 +302,15 @@ ifneq ($(LINUX_ARM),1) #PLATFORM_LIBRARIES += ICE endif ifneq ($(PLATFORM_ARCH),armv6l) - PLATFORM_LIBRARIES += X11 - PLATFORM_LIBRARIES += Xrandr - PLATFORM_LIBRARIES += Xxf86vm - PLATFORM_LIBRARIES += Xi - PLATFORM_LIBRARIES += Xcursor - PLATFORM_LIBRARIES += dl - PLATFORM_LIBRARIES += pthread + PLATFORM_LIBRARIES += X11 + PLATFORM_LIBRARIES += Xrandr + PLATFORM_LIBRARIES += Xxf86vm + PLATFORM_LIBRARIES += Xi + PLATFORM_LIBRARIES += Xinerama + PLATFORM_LIBRARIES += Xcursor + PLATFORM_LIBRARIES += dl + PLATFORM_LIBRARIES += pthread + PLATFORM_LIBRARIES += vulkan endif PLATFORM_LIBRARIES += freeimage @@ -392,6 +396,7 @@ endif ################################################################################ PLATFORM_LIBRARY_SEARCH_PATHS = +PLATFORM_LIBRARY_SEARCH_PATHS += $(VULKAN_SDK)/lib ################################################################################ # PLATFORM FRAMEWORKS diff --git a/libs/openFrameworksCompiled/project/qtcreator/modules/of/of.qbs b/libs/openFrameworksCompiled/project/qtcreator/modules/of/of.qbs index f86ceec59c7..0542289199a 100644 --- a/libs/openFrameworksCompiled/project/qtcreator/modules/of/of.qbs +++ b/libs/openFrameworksCompiled/project/qtcreator/modules/of/of.qbs @@ -194,6 +194,13 @@ Module{ })); } + // Vulkan specific: We grab the vulkan SDK install path from system environment variable + // to be sure to use the latest vulkan SDK. + // + if (platform === "linux64" || platform == "linux"){ + var vk_sdk_includes = Helpers.Environment.getEnv("VULKAN_SDK") + "/include"; + includes = includes.concat(vk_sdk_includes); + } // cflags from pkgconfigs if(platform === "linux" || platform === "linux64" || platform === "msys2"){ cflags = Helpers.pkgconfig(configs, ["--cflags-only-other"]); @@ -245,6 +252,9 @@ Module{ "dl", "pthread", "freeimage", + "boost_filesystem", + "boost_system", + "vulkan", "pugixml", ]; @@ -598,8 +608,12 @@ Module{ } coreLinkerFlags: { + // Vulkan specific: we query the vulkan sdk installation directory from + // system ENV to be sure to link against the latest SDK lib + var vk_sdk = Helpers.Environment.getEnv("VULKAN_SDK"); var flags = CORE.ldflags - .concat(linkerFlags); + .concat(linkerFlags) + .concat(['-L' + vk_sdk + '/lib']); if(of.isCoreLibrary){ return flags; diff --git a/libs/openFrameworksCompiled/project/vs/of-shaderc.props b/libs/openFrameworksCompiled/project/vs/of-shaderc.props new file mode 100644 index 00000000000..31060e95e67 --- /dev/null +++ b/libs/openFrameworksCompiled/project/vs/of-shaderc.props @@ -0,0 +1,16 @@ + + + + + + + + $(OF_ROOT)\libs\shaderc\include;%(AdditionalIncludeDirectories) + + + shaderc_combined.lib;%(AdditionalDependencies) + $(OF_ROOT)\libs\shaderc\lib\vs\x64;%(AdditionalLibraryDirectories) + + + + \ No newline at end of file diff --git a/libs/openFrameworksCompiled/project/vs/of-shadercD.props b/libs/openFrameworksCompiled/project/vs/of-shadercD.props new file mode 100644 index 00000000000..0524db0f68b --- /dev/null +++ b/libs/openFrameworksCompiled/project/vs/of-shadercD.props @@ -0,0 +1,16 @@ + + + + + + + + $(OF_ROOT)\libs\shaderc\include;%(AdditionalIncludeDirectories) + + + shaderc_combinedd.lib;%(AdditionalDependencies) + $(OF_ROOT)\libs\shaderc\lib\vs\x64;%(AdditionalLibraryDirectories) + + + + \ No newline at end of file diff --git a/libs/openFrameworksCompiled/project/vs/of-vk.props b/libs/openFrameworksCompiled/project/vs/of-vk.props new file mode 100644 index 00000000000..5de0a6138a9 --- /dev/null +++ b/libs/openFrameworksCompiled/project/vs/of-vk.props @@ -0,0 +1,16 @@ + + + + + + + + $(VK_SDK_PATH)\Include;$(OF_ROOT)\libs\openFrameworks\vk;%(AdditionalIncludeDirectories) + + + $(VK_SDK_PATH)\Lib;%(AdditionalLibraryDirectories) + vulkan-1.lib;%(AdditionalDependencies) + + + + \ No newline at end of file diff --git a/libs/openFrameworksCompiled/project/vs/openFrameworksCommon.props b/libs/openFrameworksCompiled/project/vs/openFrameworksCommon.props index 9000fe1247b..07b47da5e16 100644 --- a/libs/openFrameworksCompiled/project/vs/openFrameworksCommon.props +++ b/libs/openFrameworksCompiled/project/vs/openFrameworksCommon.props @@ -1,6 +1,8 @@  - + + + $(MSBuildThisFileDirectory)\..\..\..\..\ @@ -8,7 +10,7 @@ kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;wldap32.lib;%(AdditionalDependencies) - C:\Program Files (x86)\Visual Leak Detector\lib\Win64;%(AdditionalLibraryDirectories) + %(AdditionalLibraryDirectories) diff --git a/libs/openFrameworksCompiled/project/vs/openFrameworksDebug.props b/libs/openFrameworksCompiled/project/vs/openFrameworksDebug.props index 44d43e72934..e9978c6f6cb 100644 --- a/libs/openFrameworksCompiled/project/vs/openFrameworksDebug.props +++ b/libs/openFrameworksCompiled/project/vs/openFrameworksDebug.props @@ -1,6 +1,7 @@  + @@ -43,4 +44,4 @@ if errorlevel 1 exit 0 else exit %errorlevel% - + \ No newline at end of file diff --git a/libs/openFrameworksCompiled/project/vs/openFrameworksRelease.props b/libs/openFrameworksCompiled/project/vs/openFrameworksRelease.props index 8c799afa5b3..413778ac4ef 100644 --- a/libs/openFrameworksCompiled/project/vs/openFrameworksRelease.props +++ b/libs/openFrameworksCompiled/project/vs/openFrameworksRelease.props @@ -1,6 +1,7 @@  + @@ -26,13 +27,13 @@ if errorlevel 1 exit 0 else exit %errorlevel% - $(OF_ROOT)\libs\openFrameworks;$(OF_ROOT)\libs\openFrameworks\graphics;$(OF_ROOT)\libs\openFrameworks\app;$(OF_ROOT)\libs\openFrameworks\sound;$(OF_ROOT)\libs\openFrameworks\utils;$(OF_ROOT)\libs\openFrameworks\communication;$(OF_ROOT)\libs\openFrameworks\video;$(OF_ROOT)\libs\openFrameworks\types;$(OF_ROOT)\libs\openFrameworks\math;$(OF_ROOT)\libs\openFrameworks\3d;$(OF_ROOT)\libs\openFrameworks\gl;$(OF_ROOT)\libs\openFrameworks\events;$(OF_ROOT)\libs\glm\include;$(OF_ROOT)\libs\rtAudio\include;$(OF_ROOT)\libs\quicktime\include;$(OF_ROOT)\libs\freetype\include;$(OF_ROOT)\libs\freetype\include\freetype2;$(OF_ROOT)\libs\freeImage\include;$(OF_ROOT)\libs\fmodex\include;$(OF_ROOT)\libs\videoInput\include;$(OF_ROOT)\libs\glew\include\;$(OF_ROOT)\libs\glu\include;$(OF_ROOT)\libs\tess2\include;$(OF_ROOT)\libs\cairo\include\cairo;$(OF_ROOT)\libs\glfw\include;$(OF_ROOT)\libs\openssl\include;$(OF_ROOT)\libs\utf8\include;$(OF_ROOT)\libs\boost\include;$(OF_ROOT)\libs\json\include;$(OF_ROOT)\libs\curl\include;$(OF_ROOT)\libs\uriparser\include;$(OF_ROOT)\libs\pugixml\include;$(OF_ROOT)\addons;%(AdditionalIncludeDirectories) + $(OF_ROOT)\libs\openFrameworks;$(OF_ROOT)\libs\openFrameworks\graphics;$(OF_ROOT)\libs\openFrameworks\app;$(OF_ROOT)\libs\openFrameworks\sound;$(OF_ROOT)\libs\openFrameworks\utils;$(OF_ROOT)\libs\openFrameworks\communication;$(OF_ROOT)\libs\openFrameworks\video;$(OF_ROOT)\libs\openFrameworks\types;$(OF_ROOT)\libs\openFrameworks\math;$(OF_ROOT)\libs\openFrameworks\3d;$(OF_ROOT)\libs\openFrameworks\gl;$(OF_ROOT)\libs\openFrameworks\events;$(OF_ROOT)\libs\glm\include;$(OF_ROOT)\libs\rtAudio\include;$(OF_ROOT)\libs\quicktime\include;$(OF_ROOT)\libs\freetype\include;$(OF_ROOT)\libs\freetype\include\freetype2;$(OF_ROOT)\libs\freeImage\include;$(OF_ROOT)\libs\fmodex\include;$(OF_ROOT)\libs\videoInput\include;$(OF_ROOT)\libs\glew\include\;$(OF_ROOT)\libs\glu\include;$(OF_ROOT)\libs\tess2\include;$(OF_ROOT)\libs\cairo\include\cairo;$(OF_ROOT)\libs\glfw\include;$(OF_ROOT)\libs\openssl\include;$(OF_ROOT)\libs\utf8\include;$(OF_ROOT)\libs\boost\include;$(OF_ROOT)\libs\json\include;$(OF_ROOT)\libs\curl\include;$(OF_ROOT)\libs\uriparser\include;$(OF_ROOT)\libs\pugixml\include;$(OF_ROOT)\libs\vulkan\include;$(OF_ROOT)\libs\shaderc\include;$(OF_ROOT)\addons;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_CONSOLE;POCO_STATIC;CAIRO_WIN32_STATIC_BUILD;DISABLE_SOME_FLOATING_POINT;%(PreprocessorDefinitions) true - $(OF_ROOT)\libs\glfw\lib\vs\x64;$(OF_ROOT)\libs\rtAudio\lib\vs\x64;$(OF_ROOT)\libs\FreeImage\lib\vs\x64;$(OF_ROOT)\libs\freetype\lib\vs\x64;$(OF_ROOT)\libs\fmodex\lib\vs\x64;$(OF_ROOT)\libs\videoInput\lib\vs\x64;$(OF_ROOT)\libs\cairo\lib\vs\x64;$(OF_ROOT)\libs\glew\lib\vs\x64;$(OF_ROOT)\libs\glu\lib\vs\x64;$(OF_ROOT)\libs\openssl\lib\vs\x64;$(OF_ROOT)\libs\curl\lib\vs\x64;$(OF_ROOT)\libs\tess2\lib\vs\x64;$(OF_ROOT)\libs\boost\lib\vs\x64;$(OF_ROOT)\libs\uriparser\lib\vs\x64;$(OF_ROOT)\libs\pugixml\lib\vs\x64;%(AdditionalLibraryDirectories) - cairo-static.lib;pixman-1.lib;libpng.lib;zlib.lib;msimg32.lib;OpenGL32.lib;GLu32.lib;kernel32.lib;setupapi.lib;Vfw32.lib;comctl32.lib;rtAudio.lib;videoInput.lib;libfreetype.lib;FreeImage.lib;dsound.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;glew32s.lib;fmodex64_vc.lib;crypt32.lib;libeay32MD.lib;ssleay32MD.lib;libcurl.lib;uriparser.lib;pugixml.lib;Ws2_32.lib;tess2.lib;glfw3.lib;winmm.lib;%(AdditionalDependencies) + $(OF_ROOT)\libs\glfw\lib\vs\x64;$(OF_ROOT)\libs\rtAudio\lib\vs\x64;$(OF_ROOT)\libs\FreeImage\lib\vs\x64;$(OF_ROOT)\libs\freetype\lib\vs\x64;$(OF_ROOT)\libs\fmodex\lib\vs\x64;$(OF_ROOT)\libs\videoInput\lib\vs\x64;$(OF_ROOT)\libs\cairo\lib\vs\x64;$(OF_ROOT)\libs\glew\lib\vs\x64;$(OF_ROOT)\libs\glu\lib\vs\x64;$(OF_ROOT)\libs\openssl\lib\vs\x64;$(OF_ROOT)\libs\curl\lib\vs\x64;$(OF_ROOT)\libs\tess2\lib\vs\x64;$(OF_ROOT)\libs\boost\lib\vs\x64;$(OF_ROOT)\libs\uriparser\lib\vs\x64;$(OF_ROOT)\libs\pugixml\lib\vs\x64;$(OF_ROOT)\libs\vulkan\lib\vs\x64;$(OF_ROOT)\libs\shaderc\lib\vs\x64;%(AdditionalLibraryDirectories) + cairo-static.lib;pixman-1.lib;libpng.lib;zlib.lib;msimg32.lib;OpenGL32.lib;GLu32.lib;kernel32.lib;setupapi.lib;Vfw32.lib;comctl32.lib;rtAudio.lib;videoInput.lib;libfreetype.lib;FreeImage.lib;dsound.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;glew32s.lib;fmodex64_vc.lib;crypt32.lib;libeay32MD.lib;ssleay32MD.lib;libcurl.lib;uriparser.lib;pugixml.lib;Ws2_32.lib;tess2.lib;glfw3.lib;winmm.lib;vulkan-1.lib;shaderc_combined.lib;%(AdditionalDependencies) atlthunk.lib;LIBC.lib;LIBCMT diff --git a/libs/openFrameworksCompiled/project/vs/openframeworksLib.vcxproj b/libs/openFrameworksCompiled/project/vs/openframeworksLib.vcxproj index 6208e0252da..b16b366abde 100644 --- a/libs/openFrameworksCompiled/project/vs/openframeworksLib.vcxproj +++ b/libs/openFrameworksCompiled/project/vs/openframeworksLib.vcxproj @@ -1,5 +1,5 @@  - + Debug @@ -23,29 +23,30 @@ openframeworksLib Win32Proj 8.1 + 8.1 StaticLibrary Unicode true - v140 + v141 StaticLibrary Unicode true - v140 + v141 StaticLibrary Unicode - v140 + v141 StaticLibrary Unicode - v140 + v141 @@ -54,6 +55,7 @@ + @@ -62,6 +64,7 @@ + @@ -74,6 +77,7 @@ ..\..\lib\vs\$(Platform)\ obj\$(Platform)\$(Configuration)\ $(ProjectName)_debug + true ..\..\lib\vs\$(Platform)\ @@ -106,6 +110,7 @@ Level3 false CompileAsCpp + 4996; 4267 @@ -209,6 +214,27 @@ + + + + + + + + + + + + + + + + + + + + + @@ -275,12 +301,31 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libs/openFrameworksCompiled/project/vs/openframeworksLib.vcxproj.filters b/libs/openFrameworksCompiled/project/vs/openframeworksLib.vcxproj.filters index 186c7c58e7c..dc5ebeeaeb7 100644 --- a/libs/openFrameworksCompiled/project/vs/openframeworksLib.vcxproj.filters +++ b/libs/openFrameworksCompiled/project/vs/openframeworksLib.vcxproj.filters @@ -43,6 +43,21 @@ {5f96bf6a-8a5d-485f-a2ec-68642528d3ce} + + {71a0f1db-b957-422a-93d0-8b3123045ce2} + + + {d7762e78-b578-44fb-b582-f5648927779c} + + + {609eeb49-ba65-437e-b154-83f3f47d2782} + + + {c893a6db-10ae-4449-b208-46c17e58c303} + + + {d704b22b-d3a3-4ea9-ad09-88e1a77a04ce} + @@ -285,6 +300,69 @@ libs\openFrameworks\utils + + libs\openFrameworks\vk + + + libs\openFrameworks\vk + + + libs\openFrameworks\vk + + + libs\openFrameworks\vk + + + libs\openFrameworks\vk + + + libs\openFrameworks\vk + + + libs\openFrameworks\vk + + + libs\openFrameworks\vk + + + libs\openFrameworks\vk\Allocator + + + libs\openFrameworks\vk\Allocator + + + libs\openFrameworks\vk\Allocator + + + libs\openFrameworks\vk\spooky + + + libs\openFrameworks\vk\spirv-cross + + + libs\openFrameworks\vk\spirv-cross + + + libs\openFrameworks\vk\spirv-cross + + + libs\openFrameworks\vk\spirv-cross + + + libs\openFrameworks\vk + + + libs\openFrameworks\vk + + + libs\openFrameworks\vk + + + libs\openFrameworks\vk\spirv-cross + + + libs\openFrameworks\vk + @@ -479,6 +557,57 @@ libs\openFrameworks\utils + + libs\openFrameworks\vk + + + libs\openFrameworks\vk + + + libs\openFrameworks\vk + + + libs\openFrameworks\vk + + + libs\openFrameworks\vk + + + libs\openFrameworks\vk + + + libs\openFrameworks\vk + + + libs\openFrameworks\vk + + + libs\openFrameworks\vk\Allocator + + + libs\openFrameworks\vk\Allocator + + + libs\openFrameworks\vk\spooky + + + libs\openFrameworks\vk\spirv-cross + + + libs\openFrameworks\vk + + + libs\openFrameworks\vk + + + libs\openFrameworks\vk + + + libs\openFrameworks\vk\spirv-cross + + + libs\openFrameworks\vk + @@ -493,5 +622,11 @@ libs\openFrameworks\gl\shaders + + libs\openFrameworks\vk + + + libs\openFrameworks\vk\shaders + \ No newline at end of file diff --git a/scripts/apothecary b/scripts/apothecary index 4a25c53bcba..4c4aa96172b 160000 --- a/scripts/apothecary +++ b/scripts/apothecary @@ -1 +1 @@ -Subproject commit 4a25c53bcba14626216295e984e202192d4fc14d +Subproject commit 4c4aa96172b55104747631545d9cc15cbd91feb7 diff --git a/scripts/linux/ubuntu/install_dependencies.sh b/scripts/linux/ubuntu/install_dependencies.sh index d47e89e99e5..5b646cb83ba 100755 --- a/scripts/linux/ubuntu/install_dependencies.sh +++ b/scripts/linux/ubuntu/install_dependencies.sh @@ -145,12 +145,12 @@ fi #check if glfw3 exists apt-cache show libglfw3-dev -exit_code=$? +exit_code=1 if [ $exit_code = 0 ]; then GLFW_PKG=libglfw3-dev else echo installing glfw from source - GLFW_VER=32f38b97d544eb2fd9a568e94e37830106417b51 + GLFW_VER=4888d7d410f3a613dbf8d8e24d3bf7ad61042a12 # tools for git use GLFW_GIT_TAG=$GLFW_VER diff --git a/scripts/templates/vk/bin/data/shaders/default.frag b/scripts/templates/vk/bin/data/shaders/default.frag new file mode 100644 index 00000000000..c2595bca109 --- /dev/null +++ b/scripts/templates/vk/bin/data/shaders/default.frag @@ -0,0 +1,29 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// uniforms (resources) +layout (set = 0, binding = 0) uniform DefaultMatrices +{ + mat4 projectionMatrix; + mat4 modelMatrix; + mat4 viewMatrix; +}; + +// inputs from earlier shader stages +layout (location = 0) in vec4 inColor; +layout (location = 1) in vec3 inNormal; + +// outputs +layout (location = 0) out vec4 outFragColor; + +void main() +{ + + vec4 normalColor = vec4((inNormal + vec3(1.0)) * vec3(0.5) , 1.0); + vec4 vertexColor = inColor; + + // set the actual fragment color here + outFragColor = vertexColor; +} \ No newline at end of file diff --git a/scripts/templates/vk/bin/data/shaders/default.vert b/scripts/templates/vk/bin/data/shaders/default.vert new file mode 100644 index 00000000000..a1f215f0102 --- /dev/null +++ b/scripts/templates/vk/bin/data/shaders/default.vert @@ -0,0 +1,40 @@ +#version 450 core + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +// uniforms (resources) +layout (set = 0, binding = 0) uniform DefaultMatrices +{ + mat4 projectionMatrix; + mat4 modelMatrix; + mat4 viewMatrix; +}; // note: if you don't specify a variable name for the block its elements will live in the global namespace. + +layout (set = 0, binding = 1) uniform Style +{ + vec4 globalColor; +} style; + +// inputs (vertex attributes) +layout ( location = 0) in vec3 inPos; +layout ( location = 1) in vec3 inNormal; +layout ( location = 2) in vec2 inTexCoord; + +// outputs +layout (location = 0) out vec4 outColor; +layout (location = 1) out vec3 outNormal; + +// we override the built-in fixed function outputs +// to have more control over the SPIR-V code created. +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main() +{ + outNormal = (inverse(transpose( viewMatrix * modelMatrix)) * vec4(inNormal, 0.0)).xyz; + outColor = style.globalColor; + gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(inPos.xyz, 1.0); +} diff --git a/scripts/templates/vk/src/main.cpp b/scripts/templates/vk/src/main.cpp new file mode 100644 index 00000000000..479bf998500 --- /dev/null +++ b/scripts/templates/vk/src/main.cpp @@ -0,0 +1,42 @@ +#include "ofMain.h" +#include "ofApp.h" + +int main(){ + + // Basic initialisation (mostly setup timers, and randseed) + ofInit(); + + auto consoleLogger = new ofConsoleLoggerChannel(); + ofSetLoggerChannel( std::shared_ptr( consoleLogger, []( ofBaseLoggerChannel * lhs){} ) ); + + // Create a new window + auto mainWindow = std::make_shared(); + + // Use this instead to render using the image swapchain + // auto mainWindow = std::make_shared(); + + // Store main window in mainloop + ofGetMainLoop()->addWindow( mainWindow ); + + { + ofVkWindowSettings settings; + settings.rendererSettings.setVkVersion( 1, 0, 46 ); + settings.rendererSettings.numSwapchainImages = 3; + settings.rendererSettings.numVirtualFrames = 3; + settings.rendererSettings.presentMode = ::vk::PresentModeKHR::eMailbox; + + // Only load debug layers if app is compiled in Debug mode +#ifdef NDEBUG + settings.rendererSettings.useDebugLayers = false; +#else + settings.rendererSettings.useDebugLayers = true; +#endif + + // Initialise main window, and associated renderer. + mainWindow->setup( settings ); + } + + // Initialise and start application + ofRunApp( new ofApp() ); + +} diff --git a/scripts/templates/vk/src/ofApp.cpp b/scripts/templates/vk/src/ofApp.cpp new file mode 100644 index 00000000000..6bf6d886fcf --- /dev/null +++ b/scripts/templates/vk/src/ofApp.cpp @@ -0,0 +1,177 @@ +#include "ofApp.h" +#include "ofVkRenderer.h" + +// We keep a pointer to the renderer so we don't have to +// fetch it anew every time we need it. +ofVkRenderer* renderer; + +//-------------------------------------------------------------- +void ofApp::setup(){ + + ofDisableSetupScreen(); + + ofSetFrameRate( 0 ); + + renderer = dynamic_cast( ofGetCurrentRenderer().get() ); + + { + of::vk::Shader::Settings shaderSettings; + shaderSettings.device = renderer->getVkDevice(); + // Enable printing of verbose debug information at shader compilation + shaderSettings + .setPrintDebugInfo(true) + .setSource(::vk::ShaderStageFlagBits::eVertex, "shaders/default.vert") + .setSource(::vk::ShaderStageFlagBits::eFragment, "shaders/default.frag") + ; + + // Initialise default shader with settings above + defaultShader = std::make_shared( shaderSettings ); + + // Define pipeline state to use with draw command + of::vk::GraphicsPipelineState pipeline; + + pipeline.setShader( defaultShader ); + + pipeline.rasterizationState + .setPolygonMode( ::vk::PolygonMode::eLine ) + .setCullMode( ::vk::CullModeFlagBits::eBack ) + .setFrontFace( ::vk::FrontFace::eCounterClockwise ) + ; + + // Setup draw command using pipeline state above + defaultDraw.setup( pipeline ); + } + + mMesh = std::make_shared(ofIcoSpherePrimitive(100, 1).getMesh()); + + mCam.setupPerspective( false, 60, 0.f, 5000 ); + mCam.setPosition( { 0, 0, mCam.getImagePlaneDistance() } ); + mCam.lookAt( { 0, 0, 0 } ); + mCam.setEvents( ofEvents() ); + +} + +//-------------------------------------------------------------- +void ofApp::update(){ + +} + +//-------------------------------------------------------------- +void ofApp::draw(){ + + // Vulkan uses a slightly different clip space than OpenGL - + // In Vulkan, z goes from 0..1, instead of OpenGL's -1..1 + // and y is flipped. + // We apply the clip matrix to the projectionMatrix to transform + // from openFrameworks (GL-style) to Vulkan (Vulkan-style) clip space. + static const glm::mat4x4 clip ( + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, -1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.5f, 0.0f, + 0.0f, 0.0f, 0.5f, 1.0f + ); + + auto viewMatrix = mCam.getModelViewMatrix(); + auto projectionMatrix = clip * mCam.getProjectionMatrix( ofGetCurrentViewport() ); + auto modelMatrix = glm::rotate( float( TWO_PI * (fmodf( ofGetElapsedTimef(), 8.f) * 0.125) ), glm::normalize(glm::vec3( { 0.f, 1.f, 1.f } )) ); + + auto & context = renderer->getDefaultContext(); + + defaultDraw + .setUniform( "projectionMatrix", projectionMatrix ) + .setUniform( "viewMatrix", viewMatrix ) + .setUniform( "modelMatrix", modelMatrix ) + .setUniform( "globalColor", ofFloatColor::black) + .setDrawMethod( of::vk::DrawCommand::DrawMethod::eIndexed ) + .setMesh( mMesh ) + ; + + modelMatrix = glm::scale( modelMatrix, { 0.1,0.1,0.1 } ); + + // Setup the main pass RenderBatch + // RenderBatch is a light-weight helper object which encapsulates + // a Vulkan Command Buffer with a Vulkan RenderPass. + // + of::vk::RenderBatch::Settings settings; + settings + .setContext(context.get()) + .setFramebufferAttachmentsExtent(renderer->getSwapchain()->getWidth(), renderer->getSwapchain()->getHeight()) + .setRenderArea(::vk::Rect2D({}, { uint32_t(renderer->getViewportWidth()), uint32_t(renderer->getViewportHeight()) })) + .setRenderPass(*renderer->getDefaultRenderpass()) + .addFramebufferAttachment(context->getSwapchainImageView()) + .addClearColorValue(ofFloatColor::white) + .addFramebufferAttachment(renderer->getDepthStencilImageView()) + .addClearDepthStencilValue({ 1.f,0 }) + ; + + of::vk::RenderBatch batch{ settings }; + { + // Beginning a batch allocates a new command buffer in the context + // and begins a RenderPass + batch.begin(); + batch.draw( defaultDraw ); + // Ending a batch accumulates all draw commands into a command buffer + // and finalizes the command buffer. + batch.end(); + } + +} + +//-------------------------------------------------------------- +void ofApp::keyPressed(int key){ + +} + +//-------------------------------------------------------------- +void ofApp::keyReleased(int key){ + if ( key == ' ' ){ + if ( defaultShader ){ + defaultShader->compile(); + } + } +} + +//-------------------------------------------------------------- +void ofApp::mouseMoved(int x, int y ){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseDragged(int x, int y, int button){ + +} + +//-------------------------------------------------------------- +void ofApp::mousePressed(int x, int y, int button){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseReleased(int x, int y, int button){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseEntered(int x, int y){ + +} + +//-------------------------------------------------------------- +void ofApp::mouseExited(int x, int y){ + +} + +//-------------------------------------------------------------- +void ofApp::windowResized(int w, int h){ + mCam.setControlArea( { 0,0,float( w ),float( h ) } ); +} + +//-------------------------------------------------------------- +void ofApp::gotMessage(ofMessage msg){ + +} + +//-------------------------------------------------------------- +void ofApp::dragEvent(ofDragInfo dragInfo){ + +} diff --git a/scripts/templates/vk/src/ofApp.h b/scripts/templates/vk/src/ofApp.h new file mode 100644 index 00000000000..d37c6c02926 --- /dev/null +++ b/scripts/templates/vk/src/ofApp.h @@ -0,0 +1,32 @@ +#pragma once + +#include "ofMain.h" +#include "vk/DrawCommand.h" + +class ofApp : public ofBaseApp{ + + of::vk::DrawCommand defaultDraw; + std::shared_ptr defaultShader; + + std::shared_ptr mMesh; + + ofEasyCam mCam; + + public: + void setup(); + void update(); + void draw(); + + void keyPressed(int key); + void keyReleased(int key); + void mouseMoved(int x, int y ); + void mouseDragged(int x, int y, int button); + void mousePressed(int x, int y, int button); + void mouseReleased(int x, int y, int button); + void mouseEntered(int x, int y); + void mouseExited(int x, int y); + void windowResized(int w, int h); + void dragEvent(ofDragInfo dragInfo); + void gotMessage(ofMessage msg); + +}; diff --git a/scripts/templates/vk/template.config b/scripts/templates/vk/template.config new file mode 100644 index 00000000000..7fb199211d7 --- /dev/null +++ b/scripts/templates/vk/template.config @@ -0,0 +1,2 @@ +PLATFORMS=linux64 vs +DESCRIPTION=Vulkan application with programmable renderer