Browse Source

Merge branch 'master' of https://github.com/mosra/magnum into androidApplication_multitouch

pull/527/head
nodoteve 5 years ago
parent
commit
ba7884f78a
  1. 31
      doc/changelog.dox
  2. 10
      doc/opengl-support.dox
  3. 4
      doc/platforms-vk.dox
  4. 1
      doc/snippets/MagnumShaderTools.cpp
  5. 154
      doc/snippets/MagnumVk.cpp
  6. 14
      doc/vulkan-mapping.dox
  7. 4
      doc/vulkan-support.dox
  8. 21
      doc/vulkan-wrapping.dox
  9. 4
      package/archlinux/PKGBUILD-es2
  10. 4
      package/archlinux/PKGBUILD-es2desktop
  11. 4
      package/archlinux/PKGBUILD-es3
  12. 4
      package/archlinux/PKGBUILD-es3desktop
  13. 5
      package/ci/unix-desktop-gles.sh
  14. 2
      package/ci/unix-desktop-vulkan.sh
  15. 35
      src/Magnum/GL/AbstractShaderProgram.cpp
  16. 28
      src/Magnum/GL/AbstractShaderProgram.h
  17. 8
      src/Magnum/GL/AbstractTexture.cpp
  18. 6
      src/Magnum/GL/Context.cpp
  19. 50
      src/Magnum/GL/Extensions.h
  20. 188
      src/Magnum/GL/Implementation/MeshState.cpp
  21. 28
      src/Magnum/GL/Implementation/MeshState.h
  22. 14
      src/Magnum/GL/Implementation/ShaderProgramState.cpp
  23. 1
      src/Magnum/GL/Implementation/ShaderProgramState.h
  24. 20
      src/Magnum/GL/Implementation/ShaderState.cpp
  25. 1
      src/Magnum/GL/Implementation/ShaderState.h
  26. 65
      src/Magnum/GL/Implementation/driverSpecific.cpp
  27. 130
      src/Magnum/GL/Mesh.cpp
  28. 73
      src/Magnum/GL/Mesh.h
  29. 66
      src/Magnum/GL/MeshView.cpp
  30. 29
      src/Magnum/GL/MeshView.h
  31. 21
      src/Magnum/GL/Shader.cpp
  32. 5
      src/Magnum/GL/Shader.h
  33. 486
      src/Magnum/GL/Test/MeshGLTest.cpp
  34. 4
      src/Magnum/GL/TextureFormat.cpp
  35. 10
      src/Magnum/GL/TextureFormat.h
  36. 24
      src/Magnum/Mesh.h
  37. 13
      src/Magnum/MeshTools/sceneconverter.cpp
  38. 28
      src/Magnum/ShaderTools/AbstractConverter.cpp
  39. 56
      src/Magnum/ShaderTools/AbstractConverter.h
  40. 14
      src/Magnum/ShaderTools/CMakeLists.txt
  41. 183
      src/Magnum/ShaderTools/Implementation/spirv.h
  42. 9
      src/Magnum/ShaderTools/ShaderTools.h
  43. 61
      src/Magnum/ShaderTools/Stage.cpp
  44. 97
      src/Magnum/ShaderTools/Stage.h
  45. 12
      src/Magnum/ShaderTools/Test/AbstractConverterTest.cpp
  46. 8
      src/Magnum/ShaderTools/Test/CMakeLists.txt
  47. 265
      src/Magnum/ShaderTools/Test/SpirvTest.cpp
  48. 5
      src/Magnum/ShaderTools/Test/SpirvTestFiles/convert.sh
  49. BIN
      src/Magnum/ShaderTools/Test/SpirvTestFiles/entrypoint-interface.spv
  50. 23
      src/Magnum/ShaderTools/Test/SpirvTestFiles/entrypoint-interface.spvasm
  51. 53
      src/Magnum/ShaderTools/Test/StageTest.cpp
  52. 150
      src/Magnum/ShaderTools/shaderconverter.cpp
  53. 2
      src/Magnum/Trade/MeshData.h
  54. 19
      src/Magnum/Trade/imageconverter.cpp
  55. 2
      src/Magnum/Vk/CMakeLists.txt
  56. 6
      src/Magnum/Vk/CommandBuffer.cpp
  57. 59
      src/Magnum/Vk/CommandBuffer.h
  58. 12
      src/Magnum/Vk/Device.cpp
  59. 4
      src/Magnum/Vk/DeviceCreateInfo.h
  60. 69
      src/Magnum/Vk/DeviceFeatures.h
  61. 4
      src/Magnum/Vk/DeviceProperties.cpp
  62. 10
      src/Magnum/Vk/DeviceProperties.h
  63. 24
      src/Magnum/Vk/Enums.cpp
  64. 28
      src/Magnum/Vk/Enums.h
  65. 2
      src/Magnum/Vk/Extensions.cpp
  66. 2
      src/Magnum/Vk/Extensions.h
  67. 2
      src/Magnum/Vk/Implementation/DeviceFeatures.h
  68. 14
      src/Magnum/Vk/Implementation/DeviceState.cpp
  69. 4
      src/Magnum/Vk/Implementation/DeviceState.h
  70. 16
      src/Magnum/Vk/Implementation/DriverWorkaround.cpp
  71. 12
      src/Magnum/Vk/Implementation/deviceFeatureMapping.hpp
  72. 2
      src/Magnum/Vk/Implementation/dynamicRasterizationStateMapping.hpp
  73. 116
      src/Magnum/Vk/Implementation/spirvPatching.h
  74. 250
      src/Magnum/Vk/Mesh.cpp
  75. 413
      src/Magnum/Vk/Mesh.h
  76. 24
      src/Magnum/Vk/MeshLayout.cpp
  77. 30
      src/Magnum/Vk/MeshLayout.h
  78. 72
      src/Magnum/Vk/Pipeline.cpp
  79. 72
      src/Magnum/Vk/Pipeline.h
  80. 25
      src/Magnum/Vk/RasterizationPipelineCreateInfo.h
  81. 39
      src/Magnum/Vk/Shader.cpp
  82. 7
      src/Magnum/Vk/Shader.h
  83. 53
      src/Magnum/Vk/Test/CMakeLists.txt
  84. 8
      src/Magnum/Vk/Test/DeviceFeaturesTest.cpp
  85. 5
      src/Magnum/Vk/Test/DeviceVkTest.cpp
  86. 78
      src/Magnum/Vk/Test/EnumsTest.cpp
  87. 17
      src/Magnum/Vk/Test/MeshLayoutTest.cpp
  88. 278
      src/Magnum/Vk/Test/MeshTest.cpp
  89. 5
      src/Magnum/Vk/Test/MeshTestFiles/convert.sh
  90. BIN
      src/Magnum/Vk/Test/MeshTestFiles/flat.spv
  91. 35
      src/Magnum/Vk/Test/MeshTestFiles/flat.spvasm
  92. BIN
      src/Magnum/Vk/Test/MeshTestFiles/flat.tga
  93. BIN
      src/Magnum/Vk/Test/MeshTestFiles/noop.spv
  94. 30
      src/Magnum/Vk/Test/MeshTestFiles/noop.spvasm
  95. BIN
      src/Magnum/Vk/Test/MeshTestFiles/noop.tga
  96. BIN
      src/Magnum/Vk/Test/MeshTestFiles/nullcolor.tga
  97. BIN
      src/Magnum/Vk/Test/MeshTestFiles/vertexcolor.spv
  98. 40
      src/Magnum/Vk/Test/MeshTestFiles/vertexcolor.spvasm
  99. BIN
      src/Magnum/Vk/Test/MeshTestFiles/vertexcolor.tga
  100. 878
      src/Magnum/Vk/Test/MeshVkTest.cpp
  101. Some files were not shown because too many files have changed in this diff Show More

31
doc/changelog.dox

@ -62,11 +62,16 @@ See also:
@webgl_extension{WEBGL,debug_renderer_info} and @webgl_extension{WEBGL,debug_renderer_info} and
@webgl_extension{WEBGL,debug_shaders} extensions, no implementation done @webgl_extension{WEBGL,debug_shaders} extensions, no implementation done
yet yet
- Recognizing @webgl_extension{WEBGL,multi_draw}, - Recognizing ANGLE GLES and WebGL base vertex, base instance and multi-draw
@webgl_extension{WEBGL,draw_instanced_base_vertex_base_instance} and extensions and using them in @ref GL::AbstractShaderProgram::draw(Mesh&)
@webgl_extension{WEBGL,multi_draw_instanced_base_vertex_base_instance} and @ref GL::AbstractShaderProgram::draw(Containers::ArrayView<const Containers::Reference<MeshView>>):
extensions, however there's no Emscripten entrypoints for those yet which - @gl_extension{EXT,draw_elements_base_vertex}
means these can'be implemented right now. - @gl_extension{OES,draw_elements_base_vertex}
- @m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt)
- @m_class{m-doc-external} [ANGLE_base_vertex_base_instance](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_base_vertex_base_instance.txt)
- @webgl_extension{WEBGL,multi_draw}
- @webgl_extension{WEBGL,draw_instanced_base_vertex_base_instance}
- @webgl_extension{WEBGL,multi_draw_instanced_base_vertex_base_instance}
- Added a @ref GL::AbstractTexture::target() getter to simplify interaction - Added a @ref GL::AbstractTexture::target() getter to simplify interaction
with raw GL code with raw GL code
- Exposed @gl_extension{ARB,buffer_storage} as - Exposed @gl_extension{ARB,buffer_storage} as
@ -76,6 +81,9 @@ See also:
happening during EGL initialization in recent NVidia drivers. See happening during EGL initialization in recent NVidia drivers. See
@ref opengl-workarounds and [mosra/magnum#491](https://github.com/mosra/magnum/pull/491) @ref opengl-workarounds and [mosra/magnum#491](https://github.com/mosra/magnum/pull/491)
for more information. for more information.
- A new @cpp "angle-chatty-shader-compiler" @ce workarounds for silencing
useless linker output on ANGLE. See @ref opengl-workarounds for more
information.
@subsubsection changelog-latest-new-math Math library @subsubsection changelog-latest-new-math Math library
@ -170,6 +178,10 @@ See also:
- Added @ref GL::Framebuffer::Status::IncompleteDimensions for ES2. This enum - Added @ref GL::Framebuffer::Status::IncompleteDimensions for ES2. This enum
isn't available on ES3 or desktop GL, but NVidia drivers are known to emit isn't available on ES3 or desktop GL, but NVidia drivers are known to emit
it, which is why it got added. it, which is why it got added.
- Intel/Windows-specific code for silencing useless shader compiler output is
now advertised as a @cpp "intel-windows-chatty-shader-compiler" @ce
workaround, instead of being done silently. See @ref opengl-workarounds for
more information.
@subsubsection changelog-latest-changes-math Math library @subsubsection changelog-latest-changes-math Math library
@ -378,6 +390,11 @@ See also:
@ref Vk::pixelFormat(Magnum::CompressedPixelFormat) that return the new @ref Vk::pixelFormat(Magnum::CompressedPixelFormat) that return the new
@ref Vk::PixelFormat enum that contains only values suitable for a pixel @ref Vk::PixelFormat enum that contains only values suitable for a pixel
format format
- @cpp Vk::hasVkIndexType() @ce and @cpp Vk::vkIndexType() @ce returning a
raw @type_vk{IndexType} are deprecated in favor of
@ref Vk::meshIndexType() that returns the new @ref Vk::MeshIndexType enum.
Since all generic index types are available in Vulkan now, there's no need
for a @cpp hasMeshIndexType() @ce anymore.
- @cpp Vk::hasVkPrimitiveTopology() @ce and - @cpp Vk::hasVkPrimitiveTopology() @ce and
@cpp Vk::vkPrimitiveTopology() @ce returning a raw @cpp Vk::vkPrimitiveTopology() @ce returning a raw
@type_vk{PrimitiveToplogy} are deprecated in favor of @type_vk{PrimitiveToplogy} are deprecated in favor of
@ -430,6 +447,10 @@ See also:
@ref Corrade::Containers::ArrayView are now removed. This should have a @ref Corrade::Containers::ArrayView are now removed. This should have a
significant positive effect on compile times of code using the @ref GL, significant positive effect on compile times of code using the @ref GL,
@ref Audio, @ref Trade and @ref Text libraries @ref Audio, @ref Trade and @ref Text libraries
- @ref GL::TextureFormat::SR8 and @ref GL::TextureFormat::SRG8 were present
on ES2 builds by mistake --- the @gl_extension{EXT,texture_sRGB_R8} and
@gl_extension{EXT,texture_sRGB_RG8} extensions require OpenGL ES 3.0 at
least
- @ref SceneGraph::Object::addChild() no longer requires the type constructor - @ref SceneGraph::Object::addChild() no longer requires the type constructor
to have the last parameter a parent object object pointer, as that was to have the last parameter a parent object object pointer, as that was
quite limiting. Instead it's calling @ref SceneGraph::Object::setParent() quite limiting. Instead it's calling @ref SceneGraph::Object::setParent()

10
doc/opengl-support.dox

@ -449,6 +449,8 @@ Extension | Status
@gl_extension2{ANGLE,texture_compression_dxt1,ANGLE_texture_compression_dxt} | done @gl_extension2{ANGLE,texture_compression_dxt1,ANGLE_texture_compression_dxt} | done
@gl_extension2{ANGLE,texture_compression_dxt3,ANGLE_texture_compression_dxt} | done @gl_extension2{ANGLE,texture_compression_dxt3,ANGLE_texture_compression_dxt} | done
@gl_extension2{ANGLE,texture_compression_dxt5,ANGLE_texture_compression_dxt} | done @gl_extension2{ANGLE,texture_compression_dxt5,ANGLE_texture_compression_dxt} | done
@m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt) (unlisted) | done
@m_class{m-doc-external} [ANGLE_base_vertex_base_instance](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_base_vertex_base_instance.txt) (unlisted) | done except instanced multi-draw
@gl_extension{APPLE,texture_format_BGRA8888} | done @gl_extension{APPLE,texture_format_BGRA8888} | done
@gl_extension{APPLE,clip_distance} | done @gl_extension{APPLE,clip_distance} | done
@gl_extension{ARM,shader_framebuffer_fetch} | missing renderer setup and limit query @gl_extension{ARM,shader_framebuffer_fetch} | missing renderer setup and limit query
@ -470,6 +472,7 @@ Extension | Status
@gl_extension{EXT,texture_compression_s3tc} | done @gl_extension{EXT,texture_compression_s3tc} | done
@gl_extension{EXT,pvrtc_sRGB} | done @gl_extension{EXT,pvrtc_sRGB} | done
@gl_extension{EXT,shader_integer_mix} | done (shading language only) @gl_extension{EXT,shader_integer_mix} | done (shading language only)
@gl_extension{EXT,draw_elements_base_vertex} | done
@gl_extension{EXT,texture_norm16} | done @gl_extension{EXT,texture_norm16} | done
@gl_extension{EXT,texture_sRGB_R8} | done @gl_extension{EXT,texture_sRGB_R8} | done
@gl_extension{EXT,texture_sRGB_RG8} | done @gl_extension{EXT,texture_sRGB_RG8} | done
@ -497,6 +500,7 @@ Extension | Status
@gl_extension{OES,stencil1} | done @gl_extension{OES,stencil1} | done
@gl_extension{OES,stencil4} | done @gl_extension{OES,stencil4} | done
@gl_extension{OES,texture_float_linear} | done @gl_extension{OES,texture_float_linear} | done
@gl_extension{OES,draw_elements_base_vertex} | done
@gl_extension{OVR,multiview} | | @gl_extension{OVR,multiview} | |
@gl_extension{OVR,multiview2} | | @gl_extension{OVR,multiview2} | |
@ -567,10 +571,10 @@ Extension | Status
@webgl_extension{WEBGL,compressed_texture_pvrtc} | done @webgl_extension{WEBGL,compressed_texture_pvrtc} | done
@webgl_extension{WEBGL,compressed_texture_astc} | done @webgl_extension{WEBGL,compressed_texture_astc} | done
@webgl_extension{WEBGL,compressed_texture_s3tc_srgb} | done @webgl_extension{WEBGL,compressed_texture_s3tc_srgb} | done
@webgl_extension{WEBGL,multi_draw} | missing support in Emscripten @webgl_extension{WEBGL,multi_draw} | done
@webgl_extension{WEBGL,blend_equation_advanced_coherent} | done @webgl_extension{WEBGL,blend_equation_advanced_coherent} | done
@webgl_extension{WEBGL,draw_instanced_base_vertex_base_instance} | missing support in Emscripten @webgl_extension{WEBGL,draw_instanced_base_vertex_base_instance} | done
@webgl_extension{WEBGL,multi_draw_instanced_base_vertex_base_instance} | missing support in Emscripten @webgl_extension{WEBGL,multi_draw_instanced_base_vertex_base_instance} | done except instanced multi-draw
@section opengl-unsupported Unsupported OpenGL features @section opengl-unsupported Unsupported OpenGL features

4
doc/platforms-vk.dox

@ -75,6 +75,10 @@ Build as a static library and supply to CMake via `Vulkan_LIBRARY`:
@section platforms-vk-best-practices Vulkan best practices @section platforms-vk-best-practices Vulkan best practices
Khronos wiki:
- [Synchronization Examples](https://github.com/KhronosGroup/Vulkan-Docs/wiki/Synchronization-Examples)
NVidia tutorials and tips: NVidia tutorials and tips:
- [Engaging the Voyage to Vulkan](https://developer.nvidia.com/engaging-voyage-vulkan) - [Engaging the Voyage to Vulkan](https://developer.nvidia.com/engaging-voyage-vulkan)

1
doc/snippets/MagnumShaderTools.cpp

@ -31,6 +31,7 @@
#include "Magnum/FileCallback.h" #include "Magnum/FileCallback.h"
#include "Magnum/ShaderTools/AbstractConverter.h" #include "Magnum/ShaderTools/AbstractConverter.h"
#include "Magnum/ShaderTools/Stage.h"
#define DOXYGEN_IGNORE(...) __VA_ARGS__ #define DOXYGEN_IGNORE(...) __VA_ARGS__

154
doc/snippets/MagnumVk.cpp

@ -51,7 +51,7 @@
#include "Magnum/Vk/ImageViewCreateInfo.h" #include "Magnum/Vk/ImageViewCreateInfo.h"
#include "Magnum/Vk/LayerProperties.h" #include "Magnum/Vk/LayerProperties.h"
#include "Magnum/Vk/MemoryAllocateInfo.h" #include "Magnum/Vk/MemoryAllocateInfo.h"
#include "Magnum/Vk/MeshLayout.h" #include "Magnum/Vk/Mesh.h"
#include "Magnum/Vk/Pipeline.h" #include "Magnum/Vk/Pipeline.h"
#include "Magnum/Vk/PipelineLayout.h" #include "Magnum/Vk/PipelineLayout.h"
#include "Magnum/Vk/PixelFormat.h" #include "Magnum/Vk/PixelFormat.h"
@ -348,7 +348,7 @@ using namespace Containers::Literals;
Vk::Device device{instance, Vk::DeviceCreateInfo{DOXYGEN_IGNORE(properties)} Vk::Device device{instance, Vk::DeviceCreateInfo{DOXYGEN_IGNORE(properties)}
DOXYGEN_IGNORE() DOXYGEN_IGNORE()
.setEnabledFeatures( .setEnabledFeatures(
Vk::DeviceFeature::IndexTypeUint8| Vk::DeviceFeature::IndexTypeUnsignedByte|
Vk::DeviceFeature::SamplerAnisotropy| Vk::DeviceFeature::SamplerAnisotropy|
Vk::DeviceFeature::GeometryShader| Vk::DeviceFeature::GeometryShader|
DOXYGEN_IGNORE(Vk::DeviceFeature{})) DOXYGEN_IGNORE(Vk::DeviceFeature{}))
@ -370,7 +370,7 @@ if(extensions.isSupported("VK_NV_mesh_shader"_s))
info.addEnabledExtensions({"VK_NV_mesh_shader"_s}); info.addEnabledExtensions({"VK_NV_mesh_shader"_s});
DOXYGEN_IGNORE() DOXYGEN_IGNORE()
info.setEnabledFeatures(properties.features() & // mask away unsupported ones info.setEnabledFeatures(properties.features() & // mask away unsupported ones
(Vk::DeviceFeature::IndexTypeUint8| (Vk::DeviceFeature::IndexTypeUnsignedByte|
Vk::DeviceFeature::SamplerAnisotropy| Vk::DeviceFeature::SamplerAnisotropy|
Vk::DeviceFeature::GeometryShader| Vk::DeviceFeature::GeometryShader|
DOXYGEN_IGNORE(Vk::DeviceFeature{}))); DOXYGEN_IGNORE(Vk::DeviceFeature{})));
@ -774,25 +774,153 @@ indices.bindMemory(memory, indicesOffset);
{ {
/* [MeshLayout-usage] */ /* [MeshLayout-usage] */
constexpr UnsignedInt BufferBinding = 0; constexpr UnsignedInt Binding = 0;
constexpr UnsignedInt PositionLocation = 0; constexpr UnsignedInt PositionLocation = 0;
constexpr UnsignedInt TextureCoordinateLocation = 1; constexpr UnsignedInt TextureLocation = 1;
constexpr UnsignedInt NormalLocation = 5; constexpr UnsignedInt NormalLocation = 5;
Vk::MeshLayout meshLayout{MeshPrimitive::Triangles}; Vk::MeshLayout meshLayout{MeshPrimitive::Triangles};
meshLayout meshLayout
.addBinding(BufferBinding, .addBinding(Binding, 8*sizeof(Float))
sizeof(Vector3) + sizeof(Vector2) + sizeof(Vector3)) .addAttribute(PositionLocation, Binding, VertexFormat::Vector3, 0)
.addAttribute(PositionLocation, BufferBinding, VertexFormat::Vector3, .addAttribute(TextureLocation, Binding, VertexFormat::Vector2, 3*sizeof(Float))
0) .addAttribute(NormalLocation, Binding, VertexFormat::Vector3, 5*sizeof(Float));
.addAttribute(TextureCoordinateLocation, BufferBinding, VertexFormat::Vector2,
sizeof(Vector3))
.addAttribute(NormalLocation, BufferBinding, VertexFormat::Vector3,
sizeof(Vector3) + sizeof(Vector2));
/* [MeshLayout-usage] */ /* [MeshLayout-usage] */
} }
{
constexpr UnsignedInt Binding = 0;
constexpr UnsignedInt PositionLocation = 0;
constexpr UnsignedInt TextureLocation = 1;
constexpr UnsignedInt NormalLocation = 5;
UnsignedInt vertexCount = 35, indexCount = 48;
Vk::Device device{NoCreate};
/* [Mesh-populating] */
Vk::MeshLayout meshLayout{MeshPrimitive::Triangles};
meshLayout
.addBinding(Binding, 8*sizeof(Float))
.addAttribute(PositionLocation, Binding, VertexFormat::Vector3, 0)
.addAttribute(TextureLocation, Binding, VertexFormat::Vector2, 3*sizeof(Float))
.addAttribute(NormalLocation, Binding, VertexFormat::Vector3, 5*sizeof(Float));
Vk::Buffer vertices{DOXYGEN_IGNORE(device), Vk::BufferCreateInfo{
Vk::BufferUsage::VertexBuffer, vertexCount*8*sizeof(Float)
}, DOXYGEN_IGNORE(NoAllocate)};
DOXYGEN_IGNORE()
Vk::Mesh mesh{meshLayout};
mesh.addVertexBuffer(Binding, vertices, 0)
.setCount(vertexCount);
/* [Mesh-populating] */
/* [Mesh-populating-indexed] */
Vk::Buffer indices{DOXYGEN_IGNORE(device), Vk::BufferCreateInfo{
Vk::BufferUsage::IndexBuffer, indexCount*sizeof(UnsignedShort)
}, DOXYGEN_IGNORE(NoAllocate)};
DOXYGEN_IGNORE()
mesh.setIndexBuffer(indices, 0, MeshIndexType::UnsignedShort)
.setCount(indexCount);
/* [Mesh-populating-indexed] */
}
{
Vk::Device device{NoCreate};
/* [Mesh-populating-owned] */
Vk::Buffer buffer{DOXYGEN_IGNORE(device), Vk::BufferCreateInfo{
Vk::BufferUsage::VertexBuffer|Vk::BufferUsage::IndexBuffer, DOXYGEN_IGNORE(0)
}, DOXYGEN_IGNORE(NoAllocate)};
DOXYGEN_IGNORE()
Vk::Mesh mesh{Vk::MeshLayout{MeshPrimitive::Triangles}
.addBinding(DOXYGEN_IGNORE(0, 0))
DOXYGEN_IGNORE()
};
mesh.addVertexBuffer(DOXYGEN_IGNORE(0), buffer, DOXYGEN_IGNORE(0))
.setIndexBuffer(std::move(buffer), DOXYGEN_IGNORE(0, MeshIndexType{}))
.setCount(DOXYGEN_IGNORE(0));
/* [Mesh-populating-owned] */
}
{
Vk::Device device{NoCreate};
Vk::CommandBuffer cmd{NoCreate};
Vk::ShaderSet shaderSet;
Vk::PipelineLayout pipelineLayout{NoCreate};
Vk::RenderPass renderPass{NoCreate};
/* [Mesh-drawing] */
Vk::Mesh mesh{DOXYGEN_IGNORE(Vk::MeshLayout{MeshPrimitive{}})};
Vk::Pipeline pipeline{DOXYGEN_IGNORE(device), Vk::RasterizationPipelineCreateInfo{
DOXYGEN_IGNORE(shaderSet), mesh.layout(), DOXYGEN_IGNORE(pipelineLayout, renderPass, 0, 1)
}DOXYGEN_IGNORE()
};
DOXYGEN_IGNORE()
cmd.bindPipeline(pipeline)
.draw(mesh);
/* [Mesh-drawing] */
}
{
Vk::Device device{NoCreate};
Vk::CommandBuffer cmd{NoCreate};
Vk::ShaderSet shaderSet;
Vk::PipelineLayout pipelineLayout{NoCreate};
Vk::RenderPass renderPass{NoCreate};
constexpr UnsignedInt PositionLocation = 0;
constexpr UnsignedInt TextureLocation = 1;
constexpr UnsignedInt NormalLocation = 5;
/* [Mesh-drawing-dynamic] */
/* Use zero stride and zero offsets, as the stride gets specified dynamically
and offsets specified in concrete buffer bindings instead */
Vk::MeshLayout dynamicMeshLayout{MeshPrimitive::Triangles};
dynamicMeshLayout
.addBinding(0, 0)
.addBinding(1, 0)
.addBinding(2, 0)
.addAttribute(PositionLocation, 0, VertexFormat::Vector3, 0)
.addAttribute(TextureLocation, 1, VertexFormat::Vector2, 0)
.addAttribute(NormalLocation, 2, VertexFormat::Vector3, 0);
Vk::Pipeline pipeline{DOXYGEN_IGNORE(device), Vk::RasterizationPipelineCreateInfo{
DOXYGEN_IGNORE(shaderSet), dynamicMeshLayout, DOXYGEN_IGNORE(pipelineLayout, renderPass, 0, 1)}
/* Enable dynamic primitive and stride */
.setDynamicStates(Vk::DynamicRasterizationState::MeshPrimitive|
Vk::DynamicRasterizationState::VertexInputBindingStride)
DOXYGEN_IGNORE()
};
Vk::Buffer vertices{DOXYGEN_IGNORE(NoCreate)};
Vk::Mesh mesh{Vk::MeshLayout{MeshPrimitive::Triangles} /* Or TriangleStrip etc */
/* Concrete stride */
.addBinding(0, 8*sizeof(Float))
.addBinding(1, 8*sizeof(Float))
.addBinding(2, 8*sizeof(Float))
/* Rest the same as in the dynamicMeshLayout */
.addAttribute(PositionLocation, 0, VertexFormat::Vector3, 0)
.addAttribute(TextureLocation, 1, VertexFormat::Vector2, 0)
.addAttribute(NormalLocation, 2, VertexFormat::Vector3, 0)
};
/* Bind the same buffer to three different bindings, with concrete offsets */
mesh.addVertexBuffer(0, vertices, 0)
.addVertexBuffer(1, vertices, 3*sizeof(Float))
.addVertexBuffer(2, vertices, 5*sizeof(Float))
.setCount(DOXYGEN_IGNORE(0));
cmd.bindPipeline(pipeline)
/* Updates the dynamic primitive and stride as needed by the mesh */
.draw(mesh);
/* [Mesh-drawing-dynamic] */
}
{ {
Vk::Device device{NoCreate}; Vk::Device device{NoCreate};
/* The include should be a no-op here since it was already included above */ /* The include should be a no-op here since it was already included above */

14
doc/vulkan-mapping.dox

@ -113,9 +113,9 @@ Vulkan function | Matching API
@fn_vk{CmdBeginDebugUtilsLabelEXT} @m_class{m-label m-flat m-warning} **EXT**, \n @fn_vk{CmdEndDebugUtilsLabelEXT} @m_class{m-label m-flat m-warning} **EXT** | | @fn_vk{CmdBeginDebugUtilsLabelEXT} @m_class{m-label m-flat m-warning} **EXT**, \n @fn_vk{CmdEndDebugUtilsLabelEXT} @m_class{m-label m-flat m-warning} **EXT** | |
@fn_vk{CmdBeginRenderPass}, \n @fn_vk{CmdBeginRenderPass2} @m_class{m-label m-flat m-success} **KHR, 1.2**, \n @fn_vk{CmdNextSubpass}, \n @fn_vk{CmdNextSubpass2} @m_class{m-label m-flat m-success} **KHR, 1.2**, \n @fn_vk{CmdEndRenderpass}, \n @fn_vk{CmdEndRenderpass2} @m_class{m-label m-flat m-success} **KHR, 1.2** | @ref CommandBuffer::beginRenderPass(), \n @ref CommandBuffer::nextSubpass(), \n @ref CommandBuffer::endRenderPass() @fn_vk{CmdBeginRenderPass}, \n @fn_vk{CmdBeginRenderPass2} @m_class{m-label m-flat m-success} **KHR, 1.2**, \n @fn_vk{CmdNextSubpass}, \n @fn_vk{CmdNextSubpass2} @m_class{m-label m-flat m-success} **KHR, 1.2**, \n @fn_vk{CmdEndRenderpass}, \n @fn_vk{CmdEndRenderpass2} @m_class{m-label m-flat m-success} **KHR, 1.2** | @ref CommandBuffer::beginRenderPass(), \n @ref CommandBuffer::nextSubpass(), \n @ref CommandBuffer::endRenderPass()
@fn_vk{CmdBindDescriptorSets} | | @fn_vk{CmdBindDescriptorSets} | |
@fn_vk{CmdBindIndexBuffer} | | @fn_vk{CmdBindIndexBuffer} | internal to @ref CommandBuffer::draw()
@fn_vk{CmdBindPipeline} | @ref CommandBuffer::bindPipeline() @fn_vk{CmdBindPipeline} | @ref CommandBuffer::bindPipeline()
@fn_vk{CmdBindVertexBuffers}, \n @fn_vk{CmdBindVertexBuffers2EXT} @m_class{m-label m-flat m-warning} **EXT** | | @fn_vk{CmdBindVertexBuffers}, \n @fn_vk{CmdBindVertexBuffers2EXT} @m_class{m-label m-flat m-warning} **EXT** | internal to @ref CommandBuffer::draw()
@fn_vk{CmdBlitImage}, \n @fn_vk{CmdBlitImage2KHR} @m_class{m-label m-flat m-warning} **KHR** | | @fn_vk{CmdBlitImage}, \n @fn_vk{CmdBlitImage2KHR} @m_class{m-label m-flat m-warning} **KHR** | |
@fn_vk{CmdBuildAccelerationStructuresIndirectKHR} @m_class{m-label m-flat m-warning} **KHR** | | @fn_vk{CmdBuildAccelerationStructuresIndirectKHR} @m_class{m-label m-flat m-warning} **KHR** | |
@fn_vk{CmdBuildAccelerationStructuresKHR} @m_class{m-label m-flat m-warning} **KHR** | | @fn_vk{CmdBuildAccelerationStructuresKHR} @m_class{m-label m-flat m-warning} **KHR** | |
@ -135,8 +135,7 @@ Vulkan function | Matching API
@fn_vk{CmdDispatch} | | @fn_vk{CmdDispatch} | |
@fn_vk{CmdDispatchBase} @m_class{m-label m-flat m-success} **KHR, 1.1** | | @fn_vk{CmdDispatchBase} @m_class{m-label m-flat m-success} **KHR, 1.1** | |
@fn_vk{CmdDispatchIndirect} | | @fn_vk{CmdDispatchIndirect} | |
@fn_vk{CmdDraw} | | @fn_vk{CmdDraw}, \n @fn_vk{CmdDrawIndexed} | @ref CommandBuffer::draw()
@fn_vk{CmdDrawIndexed} | |
@fn_vk{CmdDrawIndexedIndirect} | | @fn_vk{CmdDrawIndexedIndirect} | |
@fn_vk{CmdDrawIndexedIndirectCount} @m_class{m-label m-flat m-success} **KHR, 1.2** | | @fn_vk{CmdDrawIndexedIndirectCount} @m_class{m-label m-flat m-success} **KHR, 1.2** | |
@fn_vk{CmdDrawIndirect} | | @fn_vk{CmdDrawIndirect} | |
@ -161,7 +160,7 @@ Vulkan function | Matching API
@fn_vk{CmdSetEvent} | | @fn_vk{CmdSetEvent} | |
@fn_vk{CmdSetFrontFaceEXT} @m_class{m-label m-flat m-warning} **EXT** | | @fn_vk{CmdSetFrontFaceEXT} @m_class{m-label m-flat m-warning} **EXT** | |
@fn_vk{CmdSetLineWidth} | | @fn_vk{CmdSetLineWidth} | |
@fn_vk{CmdSetPrimitiveTopologyEXT} @m_class{m-label m-flat m-warning} **EXT** | | @fn_vk{CmdSetPrimitiveTopologyEXT} @m_class{m-label m-flat m-warning} **EXT** | internal to @ref CommandBuffer::draw()
@fn_vk{CmdSetRayTracingPipelineStackSizeKHR} @m_class{m-label m-flat m-warning} **KHR** | | @fn_vk{CmdSetRayTracingPipelineStackSizeKHR} @m_class{m-label m-flat m-warning} **KHR** | |
@fn_vk{CmdSetScissor} | | @fn_vk{CmdSetScissor} | |
@fn_vk{CmdSetScissorWithCountEXT} @m_class{m-label m-flat m-warning} **EXT** | | @fn_vk{CmdSetScissorWithCountEXT} @m_class{m-label m-flat m-warning} **EXT** | |
@ -614,6 +613,7 @@ Vulkan structure | Matching API
@type_vk{PhysicalDeviceIDProperties} @m_class{m-label m-flat m-success} **KHR, 1.1** | | @type_vk{PhysicalDeviceIDProperties} @m_class{m-label m-flat m-success} **KHR, 1.1** | |
@type_vk{PhysicalDeviceImageFormatInfo2} @m_class{m-label m-flat m-success} **KHR, 1.1** | | @type_vk{PhysicalDeviceImageFormatInfo2} @m_class{m-label m-flat m-success} **KHR, 1.1** | |
@type_vk{PhysicalDeviceImagelessFramebufferFeatures} @m_class{m-label m-flat m-success} **KHR, 1.2** | @ref DeviceFeatures @type_vk{PhysicalDeviceImagelessFramebufferFeatures} @m_class{m-label m-flat m-success} **KHR, 1.2** | @ref DeviceFeatures
@type_vk{PhysicalDeviceImageRobustnessFeaturesEXT} @m_class{m-label m-flat m-warning} **EXT** | @ref DeviceFeatures
@type_vk{PhysicalDeviceIndexTypeUint8FeaturesEXT} @m_class{m-label m-flat m-warning} **EXT** | @ref DeviceFeatures @type_vk{PhysicalDeviceIndexTypeUint8FeaturesEXT} @m_class{m-label m-flat m-warning} **EXT** | @ref DeviceFeatures
@type_vk{PhysicalDeviceTextureCompressionASTCHDRFeaturesEXT} @m_class{m-label m-flat m-warning} **EXT** | @ref DeviceFeatures @type_vk{PhysicalDeviceTextureCompressionASTCHDRFeaturesEXT} @m_class{m-label m-flat m-warning} **EXT** | @ref DeviceFeatures
@type_vk{PhysicalDeviceLimits} | | @type_vk{PhysicalDeviceLimits} | |
@ -630,6 +630,8 @@ Vulkan structure | Matching API
@type_vk{PhysicalDeviceRayQueryFeaturesKHR} @m_class{m-label m-flat m-warning} **KHR** | @ref DeviceFeatures @type_vk{PhysicalDeviceRayQueryFeaturesKHR} @m_class{m-label m-flat m-warning} **KHR** | @ref DeviceFeatures
@type_vk{PhysicalDeviceRayTracingPipelineFeaturesKHR} @m_class{m-label m-flat m-warning} **KHR** | @ref DeviceFeatures @type_vk{PhysicalDeviceRayTracingPipelineFeaturesKHR} @m_class{m-label m-flat m-warning} **KHR** | @ref DeviceFeatures
@type_vk{PhysicalDeviceRayTracingPipelinePropertiesKHR} @m_class{m-label m-flat m-warning} **KHR** | | @type_vk{PhysicalDeviceRayTracingPipelinePropertiesKHR} @m_class{m-label m-flat m-warning} **KHR** | |
@type_vk{PhysicalDeviceRobustness2FeaturesEXT} @m_class{m-label m-flat m-warning} **EXT** | @ref DeviceFeatures
@type_vk{PhysicalDeviceRobustness2PropertiesEXT} @m_class{m-label m-flat m-warning} **EXT** | |
@type_vk{PhysicalDeviceSamplerFilterMinmaxProperties} @m_class{m-label m-flat m-success} **EXT, 1.2** | | @type_vk{PhysicalDeviceSamplerFilterMinmaxProperties} @m_class{m-label m-flat m-success} **EXT, 1.2** | |
@type_vk{PhysicalDeviceSamplerYcbcrConversionFeatures} @m_class{m-label m-flat m-success} **KHR, 1.1** | @ref DeviceFeatures @type_vk{PhysicalDeviceSamplerYcbcrConversionFeatures} @m_class{m-label m-flat m-success} **KHR, 1.1** | @ref DeviceFeatures
@type_vk{PhysicalDeviceSeparateDepthStencilLayoutsFeatures} @m_class{m-label m-flat m-success} **KHR, 1.2** | @ref DeviceFeatures @type_vk{PhysicalDeviceSeparateDepthStencilLayoutsFeatures} @m_class{m-label m-flat m-success} **KHR, 1.2** | @ref DeviceFeatures
@ -878,7 +880,7 @@ Vulkan enum | Matching API
@type_vk{ImageType} | not exposed, internal to @ref ImageCreateInfo subclasses @type_vk{ImageType} | not exposed, internal to @ref ImageCreateInfo subclasses
@type_vk{ImageUsageFlagBits}, \n @type_vk{ImageUsageFlags} | @ref ImageUsage, \n @ref ImageUsages @type_vk{ImageUsageFlagBits}, \n @type_vk{ImageUsageFlags} | @ref ImageUsage, \n @ref ImageUsages
@type_vk{ImageViewType} | not exposed, internal to @ref ImageViewCreateInfo subclasses @type_vk{ImageViewType} | not exposed, internal to @ref ImageViewCreateInfo subclasses
@type_vk{IndexType} | only @ref vkIndexType() @type_vk{IndexType} | @ref MeshIndexType
@type_vk{InternalAllocationType} | @ref vulkan-wrapping-host-allocation "not exposed" @type_vk{InternalAllocationType} | @ref vulkan-wrapping-host-allocation "not exposed"
@subsection vulkan-mapping-enums-l L @subsection vulkan-mapping-enums-l L

4
doc/vulkan-support.dox

@ -122,7 +122,9 @@ Extension | Status
@vk_extension{EXT,validation_features} @m_class{m-label m-info} **instance** | | @vk_extension{EXT,validation_features} @m_class{m-label m-info} **instance** | |
@vk_extension{EXT,vertex_attribute_divisor} | done @vk_extension{EXT,vertex_attribute_divisor} | done
@vk_extension{EXT,index_type_uint8} | @ref Vk::vkIndexType() only @vk_extension{EXT,index_type_uint8} | @ref Vk::vkIndexType() only
@vk_extension{EXT,extended_dynamic_state} | | @vk_extension{EXT,extended_dynamic_state} | only dynamic primitive and stride
@vk_extension{EXT,robustness2} | done except properties
@vk_extension{EXT,image_robustness} | done
@vk_extension{KHR,acceleration_structure} | | @vk_extension{KHR,acceleration_structure} | |
@vk_extension{KHR,portability_subset} | done except properties @vk_extension{KHR,portability_subset} | done except properties
@vk_extension{KHR,deferred_host_operations} | | @vk_extension{KHR,deferred_host_operations} | |

21
doc/vulkan-wrapping.dox

@ -188,5 +188,26 @@ can be also passed to @ref Vk::DeviceCreateInfo to allow reuse:
@snippet MagnumVk.cpp wrapping-optimizing-properties-device-move @snippet MagnumVk.cpp wrapping-optimizing-properties-device-move
@section vulkan-wrapping-naming-differences Important differences in naming
- To emphasise the distinction between rasterization and raytracing
pipelines and prevent confusion, @ref Vk::RasterizationPipelineCreateInfo
is used for @type_vk{GraphicsPipelineCreateInfo}, and similarly for
related APIs, such as @ref Vk::DynamicRasterizationState containing a
rasterization-related subset of @type_vk{DynamicState} or
@ref Vk::PipelineStage::AllRasterization for @val_vk{PIPELINE_STAGE_ALL_GRAPHICS_BIT,PipelineStageFlagBits}.
- Because not all @type_vk{Format} values can be used as both pixel and
vertex formats (for example there can be double-precision vertices, but not
pixels, or it makes no sense to use ASTC for vertices), the enum is split
into @ref Vk::PixelFormat and @ref Vk::VertexFormat. For pixel formats the
naming is simplified for easier typing (@ref Vk::PixelFormat::RGBA8Srgb
instead of @val_vk{FORMAT_R8G8B8A8_SRGB,Format}, for example); for vertex
formats the RGBA notion is omitted and replaced with just component count,
in most cases matching Magnum scalar and vector type names
(@ref Vk::VertexFormat::Vector3 instead of @val_vk{FORMAT_R32G32B32_SFLOAT,Format})
- As Magnum has several mesh-related abstraction (and Vulkan none),
@ref Vk::MeshPrimitive is used instead of @type_vk{PrimitiveTopology} for a
better visual grouping of related concepts
*/ */
} }

4
package/archlinux/PKGBUILD-es2

@ -53,7 +53,9 @@ build() {
check() { check() {
cd "$_rootdir/build-es2" cd "$_rootdir/build-es2"
CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5
MAGNUM_DISABLE_EXTENSIONS="GL_NV_framebuffer_multisample GL_NV_framebuffer_blit GL_EXT_robustness" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest MAGNUM_DISABLE_EXTENSIONS="GL_OES_vertex_array_object GL_NV_framebuffer_multisample GL_NV_framebuffer_blit GL_EXT_robustness GL_EXT_draw_elements_base_vertex" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest
MAGNUM_DISABLE_EXTENSIONS="GL_OES_vertex_array_object GL_NV_framebuffer_multisample GL_NV_framebuffer_blit GL_EXT_robustness GL_EXT_draw_elements_base_vertex GL_OES_draw_elements_base_vertex" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest
MAGNUM_DISABLE_EXTENSIONS="GL_OES_vertex_array_object GL_NV_framebuffer_multisample GL_NV_framebuffer_blit GL_EXT_robustness GL_EXT_draw_elements_base_vertex GL_OES_draw_elements_base_vertex GL_EXT_multi_draw_arrays GL_ANGLE_multi_draw" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest
MAGNUM_DISABLE_EXTENSIONS="GL_KHR_debug" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest MAGNUM_DISABLE_EXTENSIONS="GL_KHR_debug" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest
} }

4
package/archlinux/PKGBUILD-es2desktop

@ -57,7 +57,9 @@ build() {
check() { check() {
cd "$_rootdir/build-es2desktop" cd "$_rootdir/build-es2desktop"
CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5
MAGNUM_DISABLE_EXTENSIONS="GL_NV_framebuffer_multisample GL_NV_framebuffer_blit GL_EXT_robustness" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest MAGNUM_DISABLE_EXTENSIONS="GL_OES_vertex_array_object GL_NV_framebuffer_multisample GL_NV_framebuffer_blit GL_EXT_robustness GL_EXT_draw_elements_base_vertex" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest
MAGNUM_DISABLE_EXTENSIONS="GL_OES_vertex_array_object GL_NV_framebuffer_multisample GL_NV_framebuffer_blit GL_EXT_robustness GL_EXT_draw_elements_base_vertex GL_OES_draw_elements_base_vertex" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest
MAGNUM_DISABLE_EXTENSIONS="GL_OES_vertex_array_object GL_NV_framebuffer_multisample GL_NV_framebuffer_blit GL_EXT_robustness GL_EXT_draw_elements_base_vertex GL_OES_draw_elements_base_vertex GL_EXT_multi_draw_arrays GL_ANGLE_multi_draw" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest
MAGNUM_DISABLE_EXTENSIONS="GL_KHR_debug" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest MAGNUM_DISABLE_EXTENSIONS="GL_KHR_debug" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest
} }

4
package/archlinux/PKGBUILD-es3

@ -53,7 +53,9 @@ build() {
check() { check() {
cd "$_rootdir/build-es3" cd "$_rootdir/build-es3"
CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5
MAGNUM_DISABLE_EXTENSIONS="GL_NV_framebuffer_multisample GL_NV_framebuffer_blit GL_EXT_robustness" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest MAGNUM_DISABLE_EXTENSIONS="GL_NV_framebuffer_multisample GL_NV_framebuffer_blit GL_EXT_robustness GL_EXT_draw_elements_base_vertex" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest
MAGNUM_DISABLE_EXTENSIONS="GL_NV_framebuffer_multisample GL_NV_framebuffer_blit GL_EXT_robustness GL_EXT_draw_elements_base_vertex GL_OES_draw_elements_base_vertex GL_ANGLE_base_vertex_base_instance" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest
MAGNUM_DISABLE_EXTENSIONS="GL_NV_framebuffer_multisample GL_NV_framebuffer_blit GL_EXT_robustness GL_EXT_draw_elements_base_vertex GL_OES_draw_elements_base_vertex GL_ANGLE_base_vertex_base_instance GL_EXT_multi_draw_arrays GL_ANGLE_multi_draw" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest
MAGNUM_DISABLE_EXTENSIONS="GL_KHR_debug" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest MAGNUM_DISABLE_EXTENSIONS="GL_KHR_debug" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest
} }

4
package/archlinux/PKGBUILD-es3desktop

@ -57,7 +57,9 @@ build() {
check() { check() {
cd "$_rootdir/build-es3desktop" cd "$_rootdir/build-es3desktop"
CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5
MAGNUM_DISABLE_EXTENSIONS="GL_NV_framebuffer_multisample GL_NV_framebuffer_blit GL_EXT_robustness" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest MAGNUM_DISABLE_EXTENSIONS="GL_NV_framebuffer_multisample GL_NV_framebuffer_blit GL_EXT_robustness GL_EXT_draw_elements_base_vertex" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest
MAGNUM_DISABLE_EXTENSIONS="GL_NV_framebuffer_multisample GL_NV_framebuffer_blit GL_EXT_robustness GL_EXT_draw_elements_base_vertex GL_OES_draw_elements_base_vertex GL_ANGLE_base_vertex_base_instance" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest
MAGNUM_DISABLE_EXTENSIONS="GL_NV_framebuffer_multisample GL_NV_framebuffer_blit GL_EXT_robustness GL_EXT_draw_elements_base_vertex GL_OES_draw_elements_base_vertex GL_ANGLE_base_vertex_base_instance GL_EXT_multi_draw_arrays GL_ANGLE_multi_draw" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest
MAGNUM_DISABLE_EXTENSIONS="GL_KHR_debug" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest MAGNUM_DISABLE_EXTENSIONS="GL_KHR_debug" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest
} }

5
package/ci/unix-desktop-gles.sh

@ -57,7 +57,10 @@ cmake .. \
-G Ninja -G Ninja
ninja $NINJA_JOBS ninja $NINJA_JOBS
CORRADE_TEST_COLOR=ON ctest -V CORRADE_TEST_COLOR=ON ctest -V
if [ "$TARGET_GLES2" == "ON" ]; then CORRADE_TEST_COLOR=ON MAGNUM_DISABLE_EXTENSIONS="OES_vertex_array_object" ctest -V -R GLTest; fi MAGNUM_DISABLE_EXTENSIONS="GL_OES_vertex_array_object GL_NV_framebuffer_multisample GL_NV_framebuffer_blit GL_EXT_robustness GL_EXT_draw_elements_base_vertex" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest
MAGNUM_DISABLE_EXTENSIONS="GL_OES_vertex_array_object GL_NV_framebuffer_multisample GL_NV_framebuffer_blit GL_EXT_robustness GL_EXT_draw_elements_base_vertex GL_OES_draw_elements_base_vertex GL_ANGLE_base_vertex_base_instance" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest
MAGNUM_DISABLE_EXTENSIONS="GL_OES_vertex_array_object GL_NV_framebuffer_multisample GL_NV_framebuffer_blit GL_EXT_robustness GL_EXT_draw_elements_base_vertex GL_OES_draw_elements_base_vertex GL_ANGLE_base_vertex_base_instance GL_EXT_multi_draw_arrays GL_ANGLE_multi_draw" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest
MAGNUM_DISABLE_EXTENSIONS="GL_KHR_debug" CORRADE_TEST_COLOR=ON ctest --output-on-failure -j5 -R GLTest
Debug/bin/magnum-gl-info --limits > /dev/null Debug/bin/magnum-gl-info --limits > /dev/null
# Test install, after running the tests as for them it shouldn't be needed # Test install, after running the tests as for them it shouldn't be needed

2
package/ci/unix-desktop-vulkan.sh

@ -32,7 +32,7 @@ cmake .. \
-DCMAKE_INSTALL_PREFIX=$HOME/deps \ -DCMAKE_INSTALL_PREFIX=$HOME/deps \
-DCMAKE_BUILD_TYPE=Debug \ -DCMAKE_BUILD_TYPE=Debug \
-DWITH_AUDIO=OFF \ -DWITH_AUDIO=OFF \
-DWITH_DEBUGTOOLS=OFF \ -DWITH_DEBUGTOOLS=ON \
-DWITH_GL=OFF \ -DWITH_GL=OFF \
-DWITH_MESHTOOLS=OFF \ -DWITH_MESHTOOLS=OFF \
-DWITH_PRIMITIVES=OFF \ -DWITH_PRIMITIVES=OFF \

35
src/Magnum/GL/AbstractShaderProgram.cpp

@ -46,11 +46,6 @@
namespace Magnum { namespace GL { namespace Magnum { namespace GL {
namespace Implementation {
/* Defined in Implementation/driverSpecific.cpp */
bool isProgramLinkLogEmpty(const std::string& result);
}
Int AbstractShaderProgram::maxVertexAttributes() { Int AbstractShaderProgram::maxVertexAttributes() {
GLint& value = Context::current().state().shaderProgram->maxVertexAttributes; GLint& value = Context::current().state().shaderProgram->maxVertexAttributes;
@ -363,10 +358,8 @@ void AbstractShaderProgram::draw(Mesh& mesh) {
use(); use();
#ifndef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES2
mesh.drawInternal(mesh._count, mesh._baseVertex, mesh._instanceCount, mesh._baseInstance, mesh._indexOffset, mesh._indexStart, mesh._indexEnd); mesh.drawInternal(mesh._count, mesh._baseVertex, mesh._instanceCount, mesh._baseInstance, mesh._indexOffset, mesh._indexStart, mesh._indexEnd);
#elif !defined(MAGNUM_TARGET_GLES2)
mesh.drawInternal(mesh._count, mesh._baseVertex, mesh._instanceCount, mesh._indexOffset, mesh._indexStart, mesh._indexEnd);
#else #else
mesh.drawInternal(mesh._count, mesh._baseVertex, mesh._instanceCount, mesh._indexOffset); mesh.drawInternal(mesh._count, mesh._baseVertex, mesh._instanceCount, mesh._indexOffset);
#endif #endif
@ -380,10 +373,8 @@ void AbstractShaderProgram::draw(MeshView& mesh) {
use(); use();
#ifndef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES2
mesh._original->drawInternal(mesh._count, mesh._baseVertex, mesh._instanceCount, mesh._baseInstance, mesh._indexOffset, mesh._indexStart, mesh._indexEnd); mesh._original->drawInternal(mesh._count, mesh._baseVertex, mesh._instanceCount, mesh._baseInstance, mesh._indexOffset, mesh._indexStart, mesh._indexEnd);
#elif !defined(MAGNUM_TARGET_GLES2)
mesh._original->drawInternal(mesh._count, mesh._baseVertex, mesh._instanceCount, mesh._indexOffset, mesh._indexStart, mesh._indexEnd);
#else #else
mesh._original->drawInternal(mesh._count, mesh._baseVertex, mesh._instanceCount, mesh._indexOffset); mesh._original->drawInternal(mesh._count, mesh._baseVertex, mesh._instanceCount, mesh._indexOffset);
#endif #endif
@ -508,12 +499,16 @@ bool AbstractShaderProgram::link(std::initializer_list<Containers::Reference<Abs
glGetProgramiv(shader._id, GL_INFO_LOG_LENGTH, &logLength); glGetProgramiv(shader._id, GL_INFO_LOG_LENGTH, &logLength);
/* Error or warning message. The string is returned null-terminated, /* Error or warning message. The string is returned null-terminated,
scrap the \0 at the end afterwards */ strip the \0 at the end afterwards. */
std::string message(logLength, '\n'); std::string message(logLength, '\n');
if(message.size() > 1) if(message.size() > 1)
glGetProgramInfoLog(shader._id, message.size(), nullptr, &message[0]); glGetProgramInfoLog(shader._id, message.size(), nullptr, &message[0]);
message.resize(Math::max(logLength, 1)-1); message.resize(Math::max(logLength, 1)-1);
/* Some drivers are chatty and can't keep shut when there's nothing to
be said, handle that as well. */
Context::current().state().shaderProgram->cleanLogImplementation(message);
/* Show error log */ /* Show error log */
if(!success) { if(!success) {
Error out{Debug::Flag::NoNewlineAtTheEnd}; Error out{Debug::Flag::NoNewlineAtTheEnd};
@ -522,7 +517,7 @@ bool AbstractShaderProgram::link(std::initializer_list<Containers::Reference<Abs
out << "failed with the following message:" << Debug::newline << message; out << "failed with the following message:" << Debug::newline << message;
/* Or just warnings, if any */ /* Or just warnings, if any */
} else if(!message.empty() && !Implementation::isProgramLinkLogEmpty(message)) { } else if(!message.empty()) {
Warning out{Debug::Flag::NoNewlineAtTheEnd}; Warning out{Debug::Flag::NoNewlineAtTheEnd};
out << "GL::AbstractShaderProgram::link(): linking"; out << "GL::AbstractShaderProgram::link(): linking";
if(shaders.size() != 1) out << "of shader" << i; if(shaders.size() != 1) out << "of shader" << i;
@ -537,6 +532,20 @@ bool AbstractShaderProgram::link(std::initializer_list<Containers::Reference<Abs
return allSuccess; return allSuccess;
} }
void AbstractShaderProgram::cleanLogImplementationNoOp(std::string&) {}
#if defined(CORRADE_TARGET_WINDOWS) && !defined(MAGNUM_TARGET_GLES)
void AbstractShaderProgram::cleanLogImplementationIntelWindows(std::string& message) {
if(message == "No errors.\n") message = {};
}
#endif
#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL)
void AbstractShaderProgram::cleanLogImplementationAngle(std::string& message) {
if(message == "\n") message = {};
}
#endif
Int AbstractShaderProgram::uniformLocationInternal(const Containers::ArrayView<const char> name) { Int AbstractShaderProgram::uniformLocationInternal(const Containers::ArrayView<const char> name) {
const GLint location = glGetUniformLocation(_id, name); const GLint location = glGetUniformLocation(_id, name);
if(location == -1) if(location == -1)

28
src/Magnum/GL/AbstractShaderProgram.h

@ -840,9 +840,13 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject {
* @brief Draw multiple meshes at once * @brief Draw multiple meshes at once
* @m_since{2020,06} * @m_since{2020,06}
* *
* In OpenGL ES, if @gl_extension{EXT,multi_draw_arrays} is not * On OpenGL ES, if neither @gl_extension{EXT,multi_draw_arrays} nor
* present, the functionality is emulated using a sequence of * @m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt)
* @ref draw(MeshView&) calls. * is present, and on WebGL if @webgl_extension{WEBGL,multi_draw} is
* not present, the functionality is emulated using a sequence of
* @ref draw(MeshView&) calls. Note that @webgl_extension{WEBGL,multi_draw}
* is only implemented since Emscripten 2.0.0, so it's not even
* advertised on older versions.
* *
* If @gl_extension{ARB,vertex_array_object} (part of OpenGL 3.0), * If @gl_extension{ARB,vertex_array_object} (part of OpenGL 3.0),
* OpenGL ES 3.0, WebGL 2.0, @gl_extension{OES,vertex_array_object} in * OpenGL ES 3.0, WebGL 2.0, @gl_extension{OES,vertex_array_object} in
@ -859,8 +863,14 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject {
* @requires_gl32 Extension @gl_extension{ARB,draw_elements_base_vertex} * @requires_gl32 Extension @gl_extension{ARB,draw_elements_base_vertex}
* if the mesh is indexed and @ref MeshView::baseVertex() is not * if the mesh is indexed and @ref MeshView::baseVertex() is not
* `0` * `0`
* @requires_gl Specifying base vertex for indexed meshes is not * @requires_es_extension OpenGL ES 3.0 and extension
* available in OpenGL ES or WebGL. * @gl_extension{OES,draw_elements_base_vertex} or
* @gl_extension{EXT,draw_elements_base_vertex} if the mesh is
* indexed and @ref MeshView::baseVertex() is not `0`
* @requires_webgl_extension WebGL 2.0 and extension
* @webgl_extension{WEBGL,multi_draw_instanced_base_vertex_base_instance}
* if the mesh is indexed and @ref MeshView::baseVertex() is not
* `0`
*/ */
void draw(Containers::ArrayView<const Containers::Reference<MeshView>> meshes); void draw(Containers::ArrayView<const Containers::Reference<MeshView>> meshes);
@ -1343,6 +1353,14 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject {
#endif #endif
#endif #endif
static MAGNUM_GL_LOCAL void cleanLogImplementationNoOp(std::string& message);
#if defined(CORRADE_TARGET_WINDOWS) && !defined(MAGNUM_TARGET_GLES)
static MAGNUM_GL_LOCAL void cleanLogImplementationIntelWindows(std::string& message);
#endif
#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL)
static MAGNUM_GL_LOCAL void cleanLogImplementationAngle(std::string& message);
#endif
void use(); void use();
/* /*

8
src/Magnum/GL/AbstractTexture.cpp

@ -561,7 +561,7 @@ PixelFormat pixelFormatForInternalFormat(const TextureFormat internalFormat) {
#ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES2
case TextureFormat::R8Snorm: case TextureFormat::R8Snorm:
#endif #endif
#ifndef MAGNUM_TARGET_WEBGL #if !defined(MAGNUM_TARGET_WEBGL) && !defined(MAGNUM_TARGET_GLES2)
case TextureFormat::SR8: case TextureFormat::SR8:
#endif #endif
#ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES2
@ -611,7 +611,7 @@ PixelFormat pixelFormatForInternalFormat(const TextureFormat internalFormat) {
#ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES2
case TextureFormat::RG8Snorm: case TextureFormat::RG8Snorm:
#endif #endif
#ifdef MAGNUM_TARGET_GLES #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_GLES2)
case TextureFormat::SRG8: case TextureFormat::SRG8:
#endif #endif
#ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES2
@ -929,8 +929,10 @@ PixelType pixelTypeForInternalFormat(const TextureFormat internalFormat) {
case TextureFormat::LuminanceAlpha: case TextureFormat::LuminanceAlpha:
#endif #endif
#ifndef MAGNUM_TARGET_WEBGL #ifndef MAGNUM_TARGET_WEBGL
#ifndef MAGNUM_TARGET_GLES2
case TextureFormat::SR8: case TextureFormat::SR8:
#ifdef MAGNUM_TARGET_GLES #endif
#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_GLES2)
case TextureFormat::SRG8: case TextureFormat::SRG8:
#endif #endif
#endif #endif

6
src/Magnum/GL/Context.cpp

@ -327,6 +327,10 @@ constexpr Extension ExtensionList[]{
#ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES2
_extension(ANDROID,extension_pack_es31a), _extension(ANDROID,extension_pack_es31a),
#endif #endif
#ifndef MAGNUM_TARGET_GLES2
_extension(ANGLE,base_vertex_base_instance),
#endif
_extension(ANGLE,multi_draw),
_extension(ANGLE,texture_compression_dxt1), _extension(ANGLE,texture_compression_dxt1),
_extension(ANGLE,texture_compression_dxt3), _extension(ANGLE,texture_compression_dxt3),
_extension(ANGLE,texture_compression_dxt5), _extension(ANGLE,texture_compression_dxt5),
@ -340,6 +344,7 @@ constexpr Extension ExtensionList[]{
_extension(EXT,debug_label), _extension(EXT,debug_label),
_extension(EXT,debug_marker), _extension(EXT,debug_marker),
_extension(EXT,disjoint_timer_query), _extension(EXT,disjoint_timer_query),
_extension(EXT,draw_elements_base_vertex),
_extension(EXT,multi_draw_arrays), _extension(EXT,multi_draw_arrays),
_extension(EXT,multisampled_render_to_texture), _extension(EXT,multisampled_render_to_texture),
_extension(EXT,polygon_offset_clamp), _extension(EXT,polygon_offset_clamp),
@ -391,6 +396,7 @@ constexpr Extension ExtensionList[]{
#endif #endif
_extension(NV,texture_border_clamp), _extension(NV,texture_border_clamp),
_extension(OES,depth32), _extension(OES,depth32),
_extension(OES,draw_elements_base_vertex),
_extension(OES,mapbuffer), _extension(OES,mapbuffer),
_extension(OES,stencil1), _extension(OES,stencil1),
_extension(OES,stencil4), _extension(OES,stencil4),

50
src/Magnum/GL/Extensions.h

@ -368,21 +368,29 @@ namespace ANDROID {
#ifdef MAGNUM_TARGET_GLES2 #ifdef MAGNUM_TARGET_GLES2
_extension( 8,ANGLE,depth_texture, GLES200, GLES300) // #138 _extension( 8,ANGLE,depth_texture, GLES200, GLES300) // #138
#endif #endif
/* Unlike the WEBGL variants, these don't have a number assigned because
Google just doesn't give a shit. These are also not in the official
gl.xml but had to be fetched from an extra file inside ANGLE's repo
(which doesn't even follow the XML schema, so it had to be fixed). */
_extension( 9,ANGLE,multi_draw, GLES200, None) // #???
#ifndef MAGNUM_TARGET_GLES2
_extension( 10,ANGLE,base_vertex_base_instance, GLES310, None) // #???
#endif
} namespace APPLE { } namespace APPLE {
#ifdef MAGNUM_TARGET_GLES2 #ifdef MAGNUM_TARGET_GLES2
_extension( 9,APPLE,framebuffer_multisample, GLES200, GLES300) // #78 _extension( 11,APPLE,framebuffer_multisample, GLES200, GLES300) // #78
#endif #endif
_extension( 10,APPLE,texture_format_BGRA8888, GLES200, None) // #79 _extension( 12,APPLE,texture_format_BGRA8888, GLES200, None) // #79
#ifdef MAGNUM_TARGET_GLES2 #ifdef MAGNUM_TARGET_GLES2
_extension( 11,APPLE,texture_max_level, GLES200, GLES300) // #80 _extension( 13,APPLE,texture_max_level, GLES200, GLES300) // #80
#endif #endif
_extension( 12,APPLE,clip_distance, GLES200, None) // #193 _extension( 14,APPLE,clip_distance, GLES200, None) // #193
} namespace ARM { } namespace ARM {
#ifdef MAGNUM_TARGET_GLES2 #ifdef MAGNUM_TARGET_GLES2
_extension( 13,ARM,rgba8, GLES200, GLES300) // #82 _extension( 15,ARM,rgba8, GLES200, GLES300) // #82
#endif #endif
_extension( 14,ARM,shader_framebuffer_fetch, GLES200, None) // #165 _extension( 16,ARM,shader_framebuffer_fetch, GLES200, None) // #165
_extension( 15,ARM,shader_framebuffer_fetch_depth_stencil, GLES200, None) // #166 _extension( 17,ARM,shader_framebuffer_fetch_depth_stencil, GLES200, None) // #166
} namespace EXT { } namespace EXT {
_extension( 19,EXT,texture_filter_anisotropic, GLES200, None) // #41 _extension( 19,EXT,texture_filter_anisotropic, GLES200, None) // #41
#ifdef MAGNUM_TARGET_GLES2 #ifdef MAGNUM_TARGET_GLES2
@ -450,19 +458,22 @@ namespace ANDROID {
_extension( 59,EXT,texture_buffer, GLES310, GLES320) // #183 _extension( 59,EXT,texture_buffer, GLES310, GLES320) // #183
_extension( 60,EXT,texture_cube_map_array, GLES310, GLES320) // #184 _extension( 60,EXT,texture_cube_map_array, GLES310, GLES320) // #184
_extension( 61,EXT,primitive_bounding_box, GLES310, GLES320) // #186 _extension( 61,EXT,primitive_bounding_box, GLES310, GLES320) // #186
_extension( 62,EXT,texture_norm16, GLES310, None) // #207
_extension( 63,EXT,texture_sRGB_R8, GLES300, None) // #221
_extension( 64,EXT,texture_sRGB_RG8, GLES300, None) // #223
#endif #endif
_extension( 65,EXT,polygon_offset_clamp, GLES200, None) // #252 _extension( 62,EXT,draw_elements_base_vertex, GLES200, None) // #204
#ifndef MAGNUM_TARGET_GLES2
_extension( 63,EXT,texture_norm16, GLES310, None) // #207
_extension( 64,EXT,texture_sRGB_R8, GLES300, None) // #221
_extension( 65,EXT,texture_sRGB_RG8, GLES300, None) // #223
#endif
_extension( 66,EXT,polygon_offset_clamp, GLES200, None) // #252
#ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES2
_extension( 66,EXT,clip_cull_distance, GLES300, None) // #257 _extension( 67,EXT,clip_cull_distance, GLES300, None) // #257
_extension( 67,EXT,texture_compression_rgtc, GLES300, None) // #286 _extension( 68,EXT,texture_compression_rgtc, GLES300, None) // #286
_extension( 68,EXT,texture_compression_bptc, GLES300, None) // #287 _extension( 69,EXT,texture_compression_bptc, GLES300, None) // #287
#endif #endif
_extension( 69,EXT,texture_compression_s3tc_srgb, GLES200, None) // #289 _extension( 70,EXT,texture_compression_s3tc_srgb, GLES200, None) // #289
} namespace IMG { } namespace IMG {
_extension( 70,IMG,texture_compression_pvrtc, GLES200, None) // #54 _extension( 71,IMG,texture_compression_pvrtc, GLES200, None) // #54
} namespace KHR { } namespace KHR {
_extension( 80,KHR,texture_compression_astc_ldr,GLES200, GLES320) // #117 _extension( 80,KHR,texture_compression_astc_ldr,GLES200, GLES320) // #117
_extension( 81,KHR,texture_compression_astc_hdr,GLES200, None) // #117 _extension( 81,KHR,texture_compression_astc_hdr,GLES200, None) // #117
@ -545,14 +556,15 @@ namespace ANDROID {
#ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES2
_extension(147,OES,texture_storage_multisample_2d_array, GLES310, GLES320) // #174 _extension(147,OES,texture_storage_multisample_2d_array, GLES310, GLES320) // #174
#endif #endif
_extension(148,OES,draw_elements_base_vertex, GLES200, None) // #219
} namespace OVR { } namespace OVR {
#ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES2
_extension(148,OVR,multiview, GLES300, None) // #241 _extension(149,OVR,multiview, GLES300, None) // #241
_extension(149,OVR,multiview2, GLES300, None) // #242 _extension(150,OVR,multiview2, GLES300, None) // #242
#endif #endif
} namespace MAGNUM { } namespace MAGNUM {
#ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES2
_extension(150,MAGNUM,shader_vertex_id, GLES300, GLES300) _extension(151,MAGNUM,shader_vertex_id, GLES300, GLES300)
#endif #endif
} }
#endif #endif

188
src/Magnum/GL/Implementation/MeshState.cpp

@ -107,17 +107,185 @@ MeshState::MeshState(Context& context, ContextState& contextState, std::vector<s
} }
#endif #endif
/* Base vertex draws on ES 2/3 and WebGL 2 */
#if defined(MAGNUM_TARGET_GLES) && !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2))
#ifndef MAGNUM_TARGET_WEBGL
#ifndef MAGNUM_TARGET_GLES2
if(context.isVersionSupported(Version::GLES320)) {
drawElementsBaseVertexImplementation = glDrawElementsBaseVertex;
drawRangeElementsBaseVertexImplementation = glDrawRangeElementsBaseVertex;
drawElementsInstancedBaseVertexImplementation = glDrawElementsInstancedBaseVertex;
} else
#endif
if(context.isExtensionSupported<Extensions::EXT::draw_elements_base_vertex>()) {
extensions.push_back(Extensions::EXT::draw_elements_base_vertex::string());
drawElementsBaseVertexImplementation = glDrawElementsBaseVertexEXT;
#ifndef MAGNUM_TARGET_GLES2
drawRangeElementsBaseVertexImplementation = glDrawRangeElementsBaseVertexEXT;
drawElementsInstancedBaseVertexImplementation = glDrawElementsInstancedBaseVertexEXT;
#endif
} else if(context.isExtensionSupported<Extensions::OES::draw_elements_base_vertex>()) {
extensions.push_back(Extensions::OES::draw_elements_base_vertex::string());
drawElementsBaseVertexImplementation = glDrawElementsBaseVertexOES;
#ifndef MAGNUM_TARGET_GLES2
drawRangeElementsBaseVertexImplementation = glDrawRangeElementsBaseVertexOES;
drawElementsInstancedBaseVertexImplementation = glDrawElementsInstancedBaseVertexOES;
#endif
} else
#else
if(context.isExtensionSupported<Extensions::WEBGL::draw_instanced_base_vertex_base_instance>()) {
extensions.push_back(Extensions::WEBGL::draw_instanced_base_vertex_base_instance::string());
/* The WEBGL extension uses the same entrypoints as the ANGLE extension
it was based on, however we wrap it to supply trivial instance count
because there's no non-instanced variant. Only available since
1.39.15: https://github.com/emscripten-core/emscripten/pull/11054 */
#if __EMSCRIPTEN_major__*10000 + __EMSCRIPTEN_minor__*100 + __EMSCRIPTEN_tiny__ >= 13915
drawElementsBaseVertexImplementation = Mesh::drawElementsBaseVertexImplementationANGLE;
drawRangeElementsBaseVertexImplementation = Mesh::drawRangeElementsBaseVertexImplementationANGLE;
drawElementsInstancedBaseVertexImplementation = Mesh::drawElementsInstancedBaseVertexImplementationANGLE;
#else
/* In Context::setupDriverWorkarounds() we make sure the extension is
not even advertised, so this shouldn't be reached. */
CORRADE_INTERNAL_ASSERT_UNREACHABLE();
#endif
} else
#endif
{
drawElementsBaseVertexImplementation = Mesh::drawElementsBaseVertexImplementationAssert;
#ifndef MAGNUM_TARGET_GLES2
drawRangeElementsBaseVertexImplementation = Mesh::drawRangeElementsBaseVertexImplementationAssert;
drawElementsInstancedBaseVertexImplementation = Mesh::drawElementsInstancedBaseVertexImplementationAssert;
#endif
}
#endif
/* Base instance draws on ES3 and WebGL2 */
#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_GLES2)
#ifndef MAGNUM_TARGET_WEBGL
#ifndef MAGNUM_TARGET_GLES2
if(context.isExtensionSupported<Extensions::ANGLE::base_vertex_base_instance>()) {
extensions.push_back(Extensions::ANGLE::base_vertex_base_instance::string());
drawArraysInstancedBaseInstanceImplementation = glDrawArraysInstancedBaseInstanceANGLE;
/* This variant isn't in the ext, emulated using
glDrawElementsInstancedBaseVertexBaseInstanceANGLE */
drawElementsInstancedBaseInstanceImplementation = Mesh::drawElementsInstancedBaseInstanceImplementationANGLE;
drawElementsInstancedBaseVertexBaseInstanceImplementation = glDrawElementsInstancedBaseVertexBaseInstanceANGLE;
} else
#endif
#else
if(context.isExtensionSupported<Extensions::WEBGL::draw_instanced_base_vertex_base_instance>()) {
extensions.push_back(Extensions::WEBGL::draw_instanced_base_vertex_base_instance::string());
/* The WEBGL extension uses the same entrypoints as the ANGLE extension
it was based on. Only available since 1.39.15:
https://github.com/emscripten-core/emscripten/pull/11054 */
#if __EMSCRIPTEN_major__*10000 + __EMSCRIPTEN_minor__*100 + __EMSCRIPTEN_tiny__ >= 13915
drawArraysInstancedBaseInstanceImplementation = glDrawArraysInstancedBaseInstanceANGLE;
/* This variant isn't in the ext, emulated using
glDrawElementsInstancedBaseVertexBaseInstanceANGLE */
drawElementsInstancedBaseInstanceImplementation = Mesh::drawElementsInstancedBaseInstanceImplementationANGLE;
drawElementsInstancedBaseVertexBaseInstanceImplementation = glDrawElementsInstancedBaseVertexBaseInstanceANGLE;
#else
/* In Context::setupDriverWorkarounds() we make sure the extension is
not even advertised, so this shouldn't be reached. */
CORRADE_INTERNAL_ASSERT_UNREACHABLE();
#endif
} else
#endif
{
drawArraysInstancedBaseInstanceImplementation = Mesh::drawArraysInstancedBaseInstanceImplementationAssert;
drawElementsInstancedBaseInstanceImplementation = Mesh::drawElementsInstancedBaseInstanceImplementationAssert;
drawElementsInstancedBaseVertexBaseInstanceImplementation = Mesh::drawElementsInstancedBaseVertexBaseInstanceImplementationAssert;
}
#endif
#ifdef MAGNUM_TARGET_GLES #ifdef MAGNUM_TARGET_GLES
/* Multi draw implementation on ES. Because there's a lot of dispatch logic
involved, the multiDrawImplementationDefault then has internal
extension-specific codepaths based on whether EXT, OES, ANGLE or
whichever entrypoints are supported. */
#ifndef MAGNUM_TARGET_WEBGL
if(context.isExtensionSupported<Extensions::EXT::multi_draw_arrays>() ||
context.isExtensionSupported<Extensions::ANGLE::multi_draw>())
#else
if(context.isExtensionSupported<Extensions::WEBGL::multi_draw>())
#endif
{
#ifndef MAGNUM_TARGET_WEBGL #ifndef MAGNUM_TARGET_WEBGL
/* Multi draw implementation on ES */
if(context.isExtensionSupported<Extensions::EXT::multi_draw_arrays>()) { if(context.isExtensionSupported<Extensions::EXT::multi_draw_arrays>()) {
extensions.push_back(Extensions::EXT::multi_draw_arrays::string()); extensions.push_back(Extensions::EXT::multi_draw_arrays::string());
multiDrawImplementation = &MeshView::multiDrawImplementationDefault; multiDrawArraysImplementation = glMultiDrawArraysEXT;
} else multiDrawImplementation = &MeshView::multiDrawImplementationFallback; multiDrawElementsImplementation = glMultiDrawElementsEXT;
} else if(context.isExtensionSupported<Extensions::ANGLE::multi_draw>()) {
extensions.push_back(Extensions::ANGLE::multi_draw::string());
multiDrawArraysImplementation = glMultiDrawArraysANGLE;
multiDrawElementsImplementation = glMultiDrawElementsANGLE;
} else CORRADE_INTERNAL_ASSERT_UNREACHABLE();
#else
{
extensions.push_back(Extensions::WEBGL::multi_draw::string());
/* The WEBGL extension uses the same entrypoints as the ANGLE
extension it was based on. Only available since 2.0.0:
https://github.com/emscripten-core/emscripten/pull/11650 */
#if __EMSCRIPTEN_major__*10000 + __EMSCRIPTEN_minor__*100 + __EMSCRIPTEN_tiny__ >= 20000
multiDrawArraysImplementation = glMultiDrawArraysANGLE;
multiDrawElementsImplementation = glMultiDrawElementsANGLE;
#else
/* In Context::setupDriverWorkarounds() we make sure the extension
is not even advertised, so this shouldn't be reached. */
CORRADE_INTERNAL_ASSERT_UNREACHABLE();
#endif
}
#endif
/* These function pointers make sense only if the general multi-draw
extension is supported. Also, not on WebGL 1 at all. */
#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2))
#ifndef MAGNUM_TARGET_WEBGL
if(context.isExtensionSupported<Extensions::EXT::draw_elements_base_vertex>()) {
extensions.push_back(Extensions::EXT::draw_elements_base_vertex::string());
multiDrawElementsBaseVertexImplementation = glMultiDrawElementsBaseVertexEXT;
} else if(context.isExtensionSupported<Extensions::OES::draw_elements_base_vertex>()) {
extensions.push_back(Extensions::OES::draw_elements_base_vertex::string());
/* Yes, it's really EXT, the same as with
EXT_draw_elements_base_vertex. I have no idea why the two
extensions exist and why it isn't just one. */
multiDrawElementsBaseVertexImplementation = glMultiDrawElementsBaseVertexEXT;
} else
#else
if(context.isExtensionSupported<Extensions::WEBGL::multi_draw_instanced_base_vertex_base_instance>()) {
extensions.push_back(Extensions::WEBGL::multi_draw_instanced_base_vertex_base_instance::string());
/* The WEBGL extension uses the same entrypoints as the ANGLE
extension it was based on, however we wrap it and supply trivial
instance counts because there's no non-instanced variant. Only
available since 2.0.5: https://github.com/emscripten-core/emscripten/pull/12282 */
#if __EMSCRIPTEN_major__*10000 + __EMSCRIPTEN_minor__*100 + __EMSCRIPTEN_tiny__ >= 20005
multiDrawElementsBaseVertexImplementation = MeshView::multiDrawElementsBaseVertexImplementationANGLE;
#else #else
multiDrawImplementation = &MeshView::multiDrawImplementationFallback; /* In Context::setupDriverWorkarounds() we make sure the extension
is not even advertised, so this shouldn't be reached. */
CORRADE_INTERNAL_ASSERT_UNREACHABLE();
#endif #endif
} else
#endif
{
multiDrawElementsBaseVertexImplementation = MeshView::multiDrawElementsBaseVertexImplementationAssert;
}
#endif
multiDrawImplementation = &MeshView::multiDrawImplementationDefault;
} else multiDrawImplementation = &MeshView::multiDrawImplementationFallback;
#endif #endif
#ifdef MAGNUM_TARGET_GLES2 #ifdef MAGNUM_TARGET_GLES2
@ -125,8 +293,8 @@ MeshState::MeshState(Context& context, ContextState& contextState, std::vector<s
if(context.isExtensionSupported<Extensions::ANGLE::instanced_arrays>()) { if(context.isExtensionSupported<Extensions::ANGLE::instanced_arrays>()) {
extensions.push_back(Extensions::ANGLE::instanced_arrays::string()); extensions.push_back(Extensions::ANGLE::instanced_arrays::string());
drawArraysInstancedImplementation = &Mesh::drawArraysInstancedImplementationANGLE; drawArraysInstancedImplementation = glDrawArraysInstancedANGLE;
drawElementsInstancedImplementation = &Mesh::drawElementsInstancedImplementationANGLE; drawElementsInstancedImplementation = glDrawElementsInstancedANGLE;
} }
#ifndef MAGNUM_TARGET_WEBGL #ifndef MAGNUM_TARGET_WEBGL
else if(context.isExtensionSupported<Extensions::EXT::instanced_arrays>() || else if(context.isExtensionSupported<Extensions::EXT::instanced_arrays>() ||
@ -135,8 +303,8 @@ MeshState::MeshState(Context& context, ContextState& contextState, std::vector<s
Extensions::EXT::instanced_arrays::string() : Extensions::EXT::instanced_arrays::string() :
Extensions::EXT::draw_instanced::string()); Extensions::EXT::draw_instanced::string());
drawArraysInstancedImplementation = &Mesh::drawArraysInstancedImplementationEXT; drawArraysInstancedImplementation = glDrawArraysInstancedEXT;
drawElementsInstancedImplementation = &Mesh::drawElementsInstancedImplementationEXT; drawElementsInstancedImplementation = glDrawElementsInstancedEXT;
} else if(context.isExtensionSupported<Extensions::NV::instanced_arrays>() || } else if(context.isExtensionSupported<Extensions::NV::instanced_arrays>() ||
context.isExtensionSupported<Extensions::NV::draw_instanced>()) { context.isExtensionSupported<Extensions::NV::draw_instanced>()) {
@ -144,8 +312,8 @@ MeshState::MeshState(Context& context, ContextState& contextState, std::vector<s
Extensions::NV::instanced_arrays::string() : Extensions::NV::instanced_arrays::string() :
Extensions::NV::draw_instanced::string()); Extensions::NV::draw_instanced::string());
drawArraysInstancedImplementation = &Mesh::drawArraysInstancedImplementationNV; drawArraysInstancedImplementation = glDrawArraysInstancedNV;
drawElementsInstancedImplementation = &Mesh::drawElementsInstancedImplementationNV; drawElementsInstancedImplementation = glDrawElementsInstancedNV;
} }
#endif #endif
else { else {

28
src/Magnum/GL/Implementation/MeshState.h

@ -53,13 +53,37 @@ struct MeshState {
void(Mesh::*bindImplementation)(); void(Mesh::*bindImplementation)();
void(Mesh::*unbindImplementation)(); void(Mesh::*unbindImplementation)();
#ifdef MAGNUM_TARGET_GLES
#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2))
void(APIENTRY *drawElementsBaseVertexImplementation)(GLenum, GLsizei, GLenum, const void*, GLint);
#endif
#ifndef MAGNUM_TARGET_GLES2
void(APIENTRY *drawRangeElementsBaseVertexImplementation)(GLenum, GLuint, GLuint, GLsizei, GLenum, const void*, GLint);
#endif
#endif
#ifdef MAGNUM_TARGET_GLES2 #ifdef MAGNUM_TARGET_GLES2
void(Mesh::*drawArraysInstancedImplementation)(GLint, GLsizei, GLsizei); void(APIENTRY *drawArraysInstancedImplementation)(GLenum, GLint, GLsizei, GLsizei);
void(Mesh::*drawElementsInstancedImplementation)(GLsizei, GLintptr, GLsizei); #endif
#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_GLES2)
void(APIENTRY *drawArraysInstancedBaseInstanceImplementation)(GLenum, GLint, GLsizei, GLsizei, GLuint);
#endif
#ifdef MAGNUM_TARGET_GLES2
void(APIENTRY *drawElementsInstancedImplementation)(GLenum, GLsizei, GLenum, const void*, GLsizei);
#endif
#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_GLES2)
void(APIENTRY *drawElementsInstancedBaseVertexImplementation)(GLenum, GLsizei, GLenum, const void*, GLsizei, GLint);
void(APIENTRY *drawElementsInstancedBaseInstanceImplementation)(GLenum, GLsizei, GLenum, const void*, GLsizei, GLuint);
void(APIENTRY *drawElementsInstancedBaseVertexBaseInstanceImplementation)(GLenum, GLsizei, GLenum, const void*, GLsizei, GLint, GLuint);
#endif #endif
#ifdef MAGNUM_TARGET_GLES #ifdef MAGNUM_TARGET_GLES
void(*multiDrawImplementation)(Containers::ArrayView<const Containers::Reference<MeshView>>); void(*multiDrawImplementation)(Containers::ArrayView<const Containers::Reference<MeshView>>);
void(APIENTRY *multiDrawArraysImplementation)(GLenum, const GLint*, const GLsizei*, GLsizei);
void(APIENTRY *multiDrawElementsImplementation)(GLenum, const GLsizei*, GLenum, const void* const*, GLsizei);
#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2))
void(APIENTRY *multiDrawElementsBaseVertexImplementation)(GLenum, const GLsizei*, GLenum, const void* const*, GLsizei, const GLint*);
#endif
#endif #endif
void(*bindVAOImplementation)(GLuint); void(*bindVAOImplementation)(GLuint);

14
src/Magnum/GL/Implementation/ShaderProgramState.cpp

@ -60,6 +60,20 @@ ShaderProgramState::ShaderProgramState(Context& context, std::vector<std::string
} }
#endif #endif
#if defined(CORRADE_TARGET_WINDOWS) && !defined(MAGNUM_TARGET_GLES)
if((context.detectedDriver() & Context::DetectedDriver::IntelWindows) && !context.isDriverWorkaroundDisabled("intel-windows-chatty-shader-compiler")) {
cleanLogImplementation = &AbstractShaderProgram::cleanLogImplementationIntelWindows;
} else
#endif
#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL)
if((context.detectedDriver() & Context::DetectedDriver::Angle) && !context.isDriverWorkaroundDisabled("angle-chatty-shader-compiler")) {
cleanLogImplementation = &AbstractShaderProgram::cleanLogImplementationAngle;
} else
#endif
{
cleanLogImplementation = &AbstractShaderProgram::cleanLogImplementationNoOp;
}
#if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL)
#ifndef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES
if(context.isExtensionSupported<Extensions::ARB::separate_shader_objects>()) if(context.isExtensionSupported<Extensions::ARB::separate_shader_objects>())

1
src/Magnum/GL/Implementation/ShaderProgramState.h

@ -46,6 +46,7 @@ struct ShaderProgramState {
#ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES2
void(AbstractShaderProgram::*transformFeedbackVaryingsImplementation)(Containers::ArrayView<const std::string>, AbstractShaderProgram::TransformFeedbackBufferMode); void(AbstractShaderProgram::*transformFeedbackVaryingsImplementation)(Containers::ArrayView<const std::string>, AbstractShaderProgram::TransformFeedbackBufferMode);
#endif #endif
void(*cleanLogImplementation)(std::string&);
void(AbstractShaderProgram::*uniform1fvImplementation)(GLint, GLsizei, const GLfloat*); void(AbstractShaderProgram::*uniform1fvImplementation)(GLint, GLsizei, const GLfloat*);
void(AbstractShaderProgram::*uniform2fvImplementation)(GLint, GLsizei, const Math::Vector<2, GLfloat>*); void(AbstractShaderProgram::*uniform2fvImplementation)(GLint, GLsizei, const Math::Vector<2, GLfloat>*);

20
src/Magnum/GL/Implementation/ShaderState.cpp

@ -25,11 +25,10 @@
#include "ShaderState.h" #include "ShaderState.h"
#include "Magnum/GL/Shader.h" /* Needed only for Emscripten+pthread- / Windows+Intel-specific workarounds,
but I won't bother crafting the preprocessor logic for this. */
#if defined(CORRADE_TARGET_EMSCRIPTEN) && defined(__EMSCRIPTEN_PTHREADS__)
#include "Magnum/GL/Context.h" #include "Magnum/GL/Context.h"
#endif #include "Magnum/GL/Shader.h"
namespace Magnum { namespace GL { namespace Implementation { namespace Magnum { namespace GL { namespace Implementation {
@ -56,9 +55,18 @@ ShaderState::ShaderState(Context& context, std::vector<std::string>&):
addSourceImplementation = &Shader::addSourceImplementationDefault; addSourceImplementation = &Shader::addSourceImplementationDefault;
} }
#if !defined(CORRADE_TARGET_EMSCRIPTEN) || !defined(__EMSCRIPTEN_PTHREADS__) #if defined(CORRADE_TARGET_WINDOWS) && !defined(MAGNUM_TARGET_GLES)
static_cast<void>(context); if((context.detectedDriver() & Context::DetectedDriver::IntelWindows) && !context.isDriverWorkaroundDisabled("intel-windows-chatty-shader-compiler")) {
cleanLogImplementation = &Shader::cleanLogImplementationIntelWindows;
} else
#endif #endif
{
cleanLogImplementation = &Shader::cleanLogImplementationNoOp;
}
/* Needed only if neither of these ifdefs above hits, but I won't bother
crafting the preprocessor logic for this. */
static_cast<void>(context);
} }
}}} }}}

1
src/Magnum/GL/Implementation/ShaderState.h

@ -52,6 +52,7 @@ struct ShaderState {
}; };
void(Shader::*addSourceImplementation)(std::string); void(Shader::*addSourceImplementation)(std::string);
void(*cleanLogImplementation)(std::string&);
GLint maxVertexOutputComponents, GLint maxVertexOutputComponents,
maxFragmentInputComponents; maxFragmentInputComponents;

65
src/Magnum/GL/Implementation/driverSpecific.cpp

@ -40,6 +40,13 @@ namespace {
/* Search the code for the following strings to see where they are implemented. */ /* Search the code for the following strings to see where they are implemented. */
const char* KnownWorkarounds[]{ const char* KnownWorkarounds[]{
/* [workarounds] */ /* [workarounds] */
#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL)
/* ANGLE's shader linker insists on returning a message consisting of a
single newline on success, causing annoying noise in the console. Similar to
"intel-windows-chatty-shader-compiler". */
"angle-chatty-shader-compiler",
#endif
#if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES)
/* Calling glBufferData(), glMapBuffer(), glMapBufferRange() or glUnmapBuffer() /* Calling glBufferData(), glMapBuffer(), glMapBufferRange() or glUnmapBuffer()
on ANY buffer when ANY buffer is attached to a currently bound on ANY buffer when ANY buffer is attached to a currently bound
@ -336,6 +343,10 @@ const char* KnownWorkarounds[]{
MeshGLTest::addVertexBufferIntWithShort(). */ MeshGLTest::addVertexBufferIntWithShort(). */
"intel-windows-broken-dsa-integer-vertex-attributes", "intel-windows-broken-dsa-integer-vertex-attributes",
/* Shader compiler on Intel Windows drivers insists on telling me "No errors."
when it should just stay silent. See also "angle-chatty-shader-compiler". */
"intel-windows-chatty-shader-compiler",
/* When using more than just a vertex and fragment shader (geometry shader, /* When using more than just a vertex and fragment shader (geometry shader,
e.g.), ARB_explicit_uniform_location on Intel silently uses wrong e.g.), ARB_explicit_uniform_location on Intel silently uses wrong
locations, blowing up with either a non-descript locations, blowing up with either a non-descript
@ -382,38 +393,6 @@ const char* KnownWorkarounds[]{
} }
namespace Implementation {
/* Used in Shader.cpp (duh) */
bool isShaderCompilationLogEmpty(const std::string&);
bool isShaderCompilationLogEmpty(const std::string& result) {
#if defined(CORRADE_TARGET_WINDOWS) && !defined(MAGNUM_TARGET_GLES)
/* Intel Windows drivers are too chatty */
if((Context::current().detectedDriver() & Context::DetectedDriver::IntelWindows) && result == "No errors.\n")
return true;
#else
static_cast<void>(result);
#endif
return false;
}
/* Used in AbstractShaderProgram.cpp (duh) */
bool isProgramLinkLogEmpty(const std::string&);
bool isProgramLinkLogEmpty(const std::string& result) {
#if defined(CORRADE_TARGET_WINDOWS) && !defined(MAGNUM_TARGET_GLES)
/* Intel Windows drivers are too chatty */
if((Context::current().detectedDriver() & Context::DetectedDriver::IntelWindows) && result == "No errors.\n")
return true;
#else
static_cast<void>(result);
#endif
return false;
}
}
auto Context::detectedDriver() -> DetectedDrivers { auto Context::detectedDriver() -> DetectedDrivers {
if(_detectedDrivers) return *_detectedDrivers; if(_detectedDrivers) return *_detectedDrivers;
@ -560,6 +539,28 @@ void Context::setupDriverWorkarounds() {
_setRequiredVersion(EXT::disjoint_timer_query, None); _setRequiredVersion(EXT::disjoint_timer_query, None);
#endif #endif
#ifdef MAGNUM_TARGET_WEBGL
/* The WEBGL_multi_draw entrypoints are only available since Emscripten
2.0.0: https://github.com/emscripten-core/emscripten/pull/11650
However, the extension is advertised even on older versions and we have
no way to link to those entrypoints there. */
#if __EMSCRIPTEN_major__*10000 + __EMSCRIPTEN_minor__*100 + __EMSCRIPTEN_tiny__ < 20000
_setRequiredVersion(WEBGL::multi_draw, None);
#endif
#ifndef MAGNUM_TARGET_GLES2
/* WEBGL_multi_draw_instanced_base_vertex_base_instance only since
Emscripten 2.0.5: https://github.com/emscripten-core/emscripten/pull/12282 */
#if __EMSCRIPTEN_major__*10000 + __EMSCRIPTEN_minor__*100 + __EMSCRIPTEN_tiny__ < 20005
_setRequiredVersion(WEBGL::multi_draw_instanced_base_vertex_base_instance, None);
#endif
/* WEBGL_draw_instanced_base_vertex_base_instance only since Emscripten
1.39.15: https://github.com/emscripten-core/emscripten/pull/11054 */
#if __EMSCRIPTEN_major__*10000 + __EMSCRIPTEN_minor__*100 + __EMSCRIPTEN_tiny__ < 13915
_setRequiredVersion(WEBGL::draw_instanced_base_vertex_base_instance, None);
#endif
#endif
#endif
#undef _setRequiredVersion #undef _setRequiredVersion
#ifndef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES

130
src/Magnum/GL/Mesh.cpp

@ -401,10 +401,8 @@ Mesh& Mesh::setIndexBuffer(Buffer& buffer, const GLintptr offset, const MeshInde
return *this; return *this;
} }
#ifndef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES2
void Mesh::drawInternal(Int count, Int baseVertex, Int instanceCount, UnsignedInt baseInstance, GLintptr indexOffset, Int indexStart, Int indexEnd) void Mesh::drawInternal(Int count, Int baseVertex, Int instanceCount, UnsignedInt baseInstance, GLintptr indexOffset, Int indexStart, Int indexEnd)
#elif !defined(MAGNUM_TARGET_GLES2)
void Mesh::drawInternal(Int count, Int baseVertex, Int instanceCount, GLintptr indexOffset, Int indexStart, Int indexEnd)
#else #else
void Mesh::drawInternal(Int count, Int baseVertex, Int instanceCount, GLintptr indexOffset) void Mesh::drawInternal(Int count, Int baseVertex, Int instanceCount, GLintptr indexOffset)
#endif #endif
@ -421,15 +419,30 @@ void Mesh::drawInternal(Int count, Int baseVertex, Int instanceCount, GLintptr i
/* Indexed mesh with base vertex */ /* Indexed mesh with base vertex */
} else if(baseVertex) { } else if(baseVertex) {
#ifndef MAGNUM_TARGET_GLES #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2))
#ifndef MAGNUM_TARGET_GLES2
/* Indexed mesh with specified range */ /* Indexed mesh with specified range */
if(indexEnd) { if(indexEnd) {
glDrawRangeElementsBaseVertex(GLenum(_primitive), indexStart, indexEnd, count, GLenum(_indexType), reinterpret_cast<GLvoid*>(indexOffset), baseVertex); #ifndef MAGNUM_TARGET_GLES
glDrawRangeElementsBaseVertex
#else
state.drawRangeElementsBaseVertexImplementation
#endif
(GLenum(_primitive), indexStart, indexEnd, count, GLenum(_indexType), reinterpret_cast<GLvoid*>(indexOffset), baseVertex);
/* Indexed mesh */ /* Indexed mesh */
} else glDrawElementsBaseVertex(GLenum(_primitive), count, GLenum(_indexType), reinterpret_cast<GLvoid*>(indexOffset), baseVertex); } else
#endif
{
#ifndef MAGNUM_TARGET_GLES
glDrawElementsBaseVertex
#else
state.drawElementsBaseVertexImplementation
#endif
(GLenum(_primitive), count, GLenum(_indexType), reinterpret_cast<GLvoid*>(indexOffset), baseVertex);
}
#else #else
CORRADE_ASSERT_UNREACHABLE("GL::Mesh::draw(): desktop OpenGL is required for base vertex specification in indexed meshes", ); CORRADE_ASSERT_UNREACHABLE("GL::AbstractShaderProgram::draw(): indexed mesh draw with base vertex specification possible only since WebGL 2.0", );
#endif #endif
/* Indexed mesh */ /* Indexed mesh */
@ -457,56 +470,75 @@ void Mesh::drawInternal(Int count, Int baseVertex, Int instanceCount, GLintptr i
} else { } else {
/* Non-indexed mesh */ /* Non-indexed mesh */
if(!_indexBuffer.id()) { if(!_indexBuffer.id()) {
#ifndef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES2
/* Non-indexed mesh with base instance */ /* Non-indexed mesh with base instance */
if(baseInstance) { if(baseInstance) {
glDrawArraysInstancedBaseInstance(GLenum(_primitive), baseVertex, count, instanceCount, baseInstance); #ifndef MAGNUM_TARGET_GLES
glDrawArraysInstancedBaseInstance
#else
state.drawArraysInstancedBaseInstanceImplementation
#endif
(GLenum(_primitive), baseVertex, count, instanceCount, baseInstance);
/* Non-indexed mesh */ /* Non-indexed mesh */
} else } else
#endif #endif
{ {
#ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES2
glDrawArraysInstanced(GLenum(_primitive), baseVertex, count, instanceCount); glDrawArraysInstanced
#else #else
(this->*state.drawArraysInstancedImplementation)(baseVertex, count, instanceCount); state.drawArraysInstancedImplementation
#endif #endif
(GLenum(_primitive), baseVertex, count, instanceCount);
} }
/* Indexed mesh with base vertex */ /* Indexed mesh with base vertex */
} else if(baseVertex) { } else if(baseVertex) {
#if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) #ifndef MAGNUM_TARGET_GLES2
#ifndef MAGNUM_TARGET_GLES
/* Indexed mesh with base vertex and base instance */ /* Indexed mesh with base vertex and base instance */
if(baseInstance) { if(baseInstance) {
glDrawElementsInstancedBaseVertexBaseInstance(GLenum(_primitive), count, GLenum(_indexType), reinterpret_cast<GLvoid*>(indexOffset), instanceCount, baseVertex, baseInstance); #ifndef MAGNUM_TARGET_GLES
glDrawElementsInstancedBaseVertexBaseInstance
#else
state.drawElementsInstancedBaseVertexBaseInstanceImplementation
#endif
(GLenum(_primitive), count, GLenum(_indexType), reinterpret_cast<GLvoid*>(indexOffset), instanceCount, baseVertex, baseInstance);
/* Indexed mesh with base vertex */ /* Indexed mesh with base vertex */
} else } else {
#ifndef MAGNUM_TARGET_GLES
glDrawElementsInstancedBaseVertex
#else
state.drawElementsInstancedBaseVertexImplementation
#endif #endif
{ (GLenum(_primitive), count, GLenum(_indexType), reinterpret_cast<GLvoid*>(indexOffset), instanceCount, baseVertex);
glDrawElementsInstancedBaseVertex(GLenum(_primitive), count, GLenum(_indexType), reinterpret_cast<GLvoid*>(indexOffset), instanceCount, baseVertex);
} }
#else #else
CORRADE_ASSERT_UNREACHABLE("GL::Mesh::draw(): OpenGL ES 3.2 or desktop GL is required for base vertex specification in indexed meshes", ); CORRADE_ASSERT_UNREACHABLE("GL::AbstractShaderProgram::draw(): instanced indexed mesh draw with base vertex specification possible only since OpenGL ES 3.0", );
#endif #endif
/* Indexed mesh */ /* Indexed mesh */
} else { } else {
#ifndef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES2
/* Indexed mesh with base instance */ /* Indexed mesh with base instance */
if(baseInstance) { if(baseInstance) {
glDrawElementsInstancedBaseInstance(GLenum(_primitive), count, GLenum(_indexType), reinterpret_cast<GLvoid*>(indexOffset), instanceCount, baseInstance); #ifndef MAGNUM_TARGET_GLES
glDrawElementsInstancedBaseInstance
#else
state.drawElementsInstancedBaseInstanceImplementation
#endif
(GLenum(_primitive), count, GLenum(_indexType), reinterpret_cast<GLvoid*>(indexOffset), instanceCount, baseInstance);
/* Instanced mesh */ /* Instanced mesh */
} else } else
#endif #endif
{ {
#ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES2
glDrawElementsInstanced(GLenum(_primitive), count, GLenum(_indexType), reinterpret_cast<GLvoid*>(indexOffset), instanceCount); glDrawElementsInstanced
#else #else
(this->*state.drawElementsInstancedImplementation)(count, indexOffset, instanceCount); state.drawElementsInstancedImplementation
#endif #endif
(GLenum(_primitive), count, GLenum(_indexType), reinterpret_cast<GLvoid*>(indexOffset), instanceCount);
} }
} }
} }
@ -871,32 +903,56 @@ void Mesh::unbindImplementationDefault() {
void Mesh::unbindImplementationVAO() {} void Mesh::unbindImplementationVAO() {}
#ifdef MAGNUM_TARGET_GLES2 #ifdef MAGNUM_TARGET_GLES
void Mesh::drawArraysInstancedImplementationANGLE(const GLint baseVertex, const GLsizei count, const GLsizei instanceCount) { #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2))
glDrawArraysInstancedANGLE(GLenum(_primitive), baseVertex, count, instanceCount); #if defined(MAGNUM_TARGET_WEBGL) && __EMSCRIPTEN_major__*10000 + __EMSCRIPTEN_minor__*100 + __EMSCRIPTEN_patch__ >= 13915
void Mesh::drawElementsBaseVertexImplementationANGLE(GLenum mode, GLsizei count, GLenum type, const void* indices, GLint baseVertex) {
glDrawElementsInstancedBaseVertexBaseInstanceANGLE(mode, count, type, indices, 1, baseVertex, 0);
} }
#endif
#ifndef MAGNUM_TARGET_WEBGL void Mesh::drawElementsBaseVertexImplementationAssert(GLenum, GLsizei, GLenum, const void*, GLint) {
void Mesh::drawArraysInstancedImplementationEXT(const GLint baseVertex, const GLsizei count, const GLsizei instanceCount) { CORRADE_ASSERT_UNREACHABLE("GL::AbstractShaderProgram::draw(): no extension available for indexed mesh draw with base vertex specification", );
glDrawArraysInstancedEXT(GLenum(_primitive), baseVertex, count, instanceCount);
} }
#endif
void Mesh::drawArraysInstancedImplementationNV(const GLint baseVertex, const GLsizei count, const GLsizei instanceCount) { #ifndef MAGNUM_TARGET_GLES2
glDrawArraysInstancedNV(GLenum(_primitive), baseVertex, count, instanceCount); #if defined(MAGNUM_TARGET_WEBGL) && __EMSCRIPTEN_major__*10000 + __EMSCRIPTEN_minor__*100 + __EMSCRIPTEN_patch__ >= 13915
void Mesh::drawRangeElementsBaseVertexImplementationANGLE(const GLenum mode, GLuint, GLuint, GLsizei count, GLenum type, const void* indices, GLint baseVertex) {
glDrawElementsInstancedBaseVertexBaseInstanceANGLE(mode, count, type, indices, 1, baseVertex, 0);
} }
#endif #endif
void Mesh::drawElementsInstancedImplementationANGLE(const GLsizei count, const GLintptr indexOffset, const GLsizei instanceCount) { void Mesh::drawRangeElementsBaseVertexImplementationAssert(GLenum, GLuint, GLuint, GLsizei, GLenum, const void*, GLint) {
glDrawElementsInstancedANGLE(GLenum(_primitive), count, GLenum(_indexType), reinterpret_cast<GLvoid*>(indexOffset), instanceCount); CORRADE_ASSERT_UNREACHABLE("GL::AbstractShaderProgram::draw(): no extension available for indexed mesh draw with base vertex specification", );
} }
#ifndef MAGNUM_TARGET_WEBGL void Mesh::drawArraysInstancedBaseInstanceImplementationAssert(GLenum, GLint, GLsizei, GLsizei, GLuint) {
void Mesh::drawElementsInstancedImplementationEXT(const GLsizei count, const GLintptr indexOffset, const GLsizei instanceCount) { CORRADE_ASSERT_UNREACHABLE("GL::AbstractShaderProgram::draw(): no extension available for instanced mesh draw with base instance specification", );
glDrawElementsInstancedEXT(GLenum(_primitive), count, GLenum(_indexType), reinterpret_cast<GLvoid*>(indexOffset), instanceCount); }
#if !defined(MAGNUM_TARGET_WEBGL) || __EMSCRIPTEN_major__*10000 + __EMSCRIPTEN_minor__*100 + __EMSCRIPTEN_patch__ >= 13915
void Mesh::drawElementsInstancedBaseInstanceImplementationANGLE(const GLenum mode, const GLsizei count, const GLenum type, const void* const indices, const GLsizei instanceCount, const GLuint baseInstance) {
glDrawElementsInstancedBaseVertexBaseInstanceANGLE(mode, count, type, indices, instanceCount, 0, baseInstance);
}
#endif
void Mesh::drawElementsInstancedBaseInstanceImplementationAssert(GLenum, GLsizei, GLenum, const void*, GLsizei, GLuint) {
CORRADE_ASSERT_UNREACHABLE("GL::AbstractShaderProgram::draw(): no extension available for instanced indexed mesh draw with base instance specification", );
}
void Mesh::drawElementsInstancedBaseVertexBaseInstanceImplementationAssert(GLenum, GLsizei, GLenum, const void*, GLsizei, GLint, GLuint) {
CORRADE_ASSERT_UNREACHABLE("GL::AbstractShaderProgram::draw(): no extension available for instanced indexed mesh draw with base vertex and base instance specification", );
} }
void Mesh::drawElementsInstancedImplementationNV(const GLsizei count, const GLintptr indexOffset, const GLsizei instanceCount) { #if defined(MAGNUM_TARGET_WEBGL) && __EMSCRIPTEN_major__*10000 + __EMSCRIPTEN_minor__*100 + __EMSCRIPTEN_patch__ >= 13915
glDrawElementsInstancedNV(GLenum(_primitive), count, GLenum(_indexType), reinterpret_cast<GLvoid*>(indexOffset), instanceCount); void Mesh::drawElementsInstancedBaseVertexImplementationANGLE(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instanceCount, GLint baseVertex) {
glDrawElementsInstancedBaseVertexBaseInstanceANGLE(mode, count, type, indices, instanceCount, baseVertex, 0);
}
#endif
void Mesh::drawElementsInstancedBaseVertexImplementationAssert(GLenum, GLsizei, GLenum, const void*, GLsizei, GLint) {
CORRADE_ASSERT_UNREACHABLE("GL::AbstractShaderProgram::draw(): no extension available for instanced indexed mesh draw with base vertex specification", );
} }
#endif #endif
#endif #endif

73
src/Magnum/GL/Mesh.h

@ -172,11 +172,20 @@ MAGNUM_GL_EXPORT MeshPrimitive meshPrimitive(Magnum::MeshPrimitive primitive);
@m_enum_values_as_keywords @m_enum_values_as_keywords
*/ */
enum class MeshIndexType: GLenum { enum class MeshIndexType: GLenum {
UnsignedByte = GL_UNSIGNED_BYTE, /**< Unsigned byte */ /**
UnsignedShort = GL_UNSIGNED_SHORT, /**< Unsigned short */ * @relativeref{Magnum,UnsignedByte}.
*
* Even though OpenGL historically supports 8-bit indices, using this type
* is discouraged on contemporary GPU architectures. Prefer using 16-bit
* indices instead.
*/
UnsignedByte = GL_UNSIGNED_BYTE,
/** @relativeref{Magnum,UnsignedShort} */
UnsignedShort = GL_UNSIGNED_SHORT,
/** /**
* Unsigned int * @relativeref{Magnum,UnsignedInt}
* @requires_gles30 Extension @gl_extension{OES,element_index_uint} * @requires_gles30 Extension @gl_extension{OES,element_index_uint}
* in OpenGL ES 2.0. * in OpenGL ES 2.0.
* @requires_webgl20 Extension @webgl_extension{OES,element_index_uint} * @requires_webgl20 Extension @webgl_extension{OES,element_index_uint}
@ -608,8 +617,12 @@ class MAGNUM_GL_EXPORT Mesh: public AbstractObject {
* @see @ref setCount(), @ref setBaseInstance() * @see @ref setCount(), @ref setBaseInstance()
* @requires_gl32 Extension @gl_extension{ARB,draw_elements_base_vertex} * @requires_gl32 Extension @gl_extension{ARB,draw_elements_base_vertex}
* for indexed meshes * for indexed meshes
* @requires_gles32 Base vertex cannot be specified for indexed meshes * @requires_gles32 Extension @gl_extension{OES,draw_elements_base_vertex}
* in OpenGL ES 3.1 or WebGL. * or @gl_extension{EXT,draw_elements_base_vertex} for indexed
* meshes on OpenGL ES 3.1 and older
* @requires_webgl_extension WebGL 2.0 and extension
* @webgl_extension{WEBGL,draw_instanced_base_vertex_base_instance}
* for indexed meshes
*/ */
Mesh& setBaseVertex(Int baseVertex) { Mesh& setBaseVertex(Int baseVertex) {
_baseVertex = baseVertex; _baseVertex = baseVertex;
@ -647,7 +660,7 @@ class MAGNUM_GL_EXPORT Mesh: public AbstractObject {
return *this; return *this;
} }
#ifndef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES2
/** @brief Base instance */ /** @brief Base instance */
UnsignedInt baseInstance() const { return _baseInstance; } UnsignedInt baseInstance() const { return _baseInstance; }
@ -659,8 +672,10 @@ class MAGNUM_GL_EXPORT Mesh: public AbstractObject {
* Default is @cpp 0 @ce. * Default is @cpp 0 @ce.
* @see @ref setInstanceCount(), @ref setBaseVertex() * @see @ref setInstanceCount(), @ref setBaseVertex()
* @requires_gl42 Extension @gl_extension{ARB,base_instance} * @requires_gl42 Extension @gl_extension{ARB,base_instance}
* @requires_gl Base instance cannot be specified in OpenGL ES or * @requires_es_extension OpenGL ES 3.1 and extension
* WebGL. * @m_class{m-doc-external} [ANGLE_base_vertex_base_instance](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_base_vertex_base_instance.txt)
* @requires_webgl_extension WebGL 2.0 and extension
* @webgl_extension{WEBGL,draw_instanced_base_vertex_base_instance}
*/ */
Mesh& setBaseInstance(UnsignedInt baseInstance) { Mesh& setBaseInstance(UnsignedInt baseInstance) {
_baseInstance = baseInstance; _baseInstance = baseInstance;
@ -1068,10 +1083,8 @@ class MAGNUM_GL_EXPORT Mesh: public AbstractObject {
void MAGNUM_GL_LOCAL bindVAO(); void MAGNUM_GL_LOCAL bindVAO();
#ifndef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES2
void drawInternal(Int count, Int baseVertex, Int instanceCount, UnsignedInt baseInstance, GLintptr indexOffset, Int indexStart, Int indexEnd); void drawInternal(Int count, Int baseVertex, Int instanceCount, UnsignedInt baseInstance, GLintptr indexOffset, Int indexStart, Int indexEnd);
#elif !defined(MAGNUM_TARGET_GLES2)
void drawInternal(Int count, Int baseVertex, Int instanceCount, GLintptr indexOffset, Int indexStart, Int indexEnd);
#else #else
void drawInternal(Int count, Int baseVertex, Int instanceCount, GLintptr indexOffset); void drawInternal(Int count, Int baseVertex, Int instanceCount, GLintptr indexOffset);
#endif #endif
@ -1133,17 +1146,33 @@ class MAGNUM_GL_EXPORT Mesh: public AbstractObject {
void MAGNUM_GL_LOCAL unbindImplementationDefault(); void MAGNUM_GL_LOCAL unbindImplementationDefault();
void MAGNUM_GL_LOCAL unbindImplementationVAO(); void MAGNUM_GL_LOCAL unbindImplementationVAO();
#ifdef MAGNUM_TARGET_GLES2 #ifdef MAGNUM_TARGET_GLES
void MAGNUM_GL_LOCAL drawArraysInstancedImplementationANGLE(GLint baseVertex, GLsizei count, GLsizei instanceCount); #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2))
#ifndef MAGNUM_TARGET_WEBGL #if defined(MAGNUM_TARGET_WEBGL) && __EMSCRIPTEN_major__*10000 + __EMSCRIPTEN_minor__*100 + __EMSCRIPTEN_tiny__ >= 13915
void MAGNUM_GL_LOCAL drawArraysInstancedImplementationEXT(GLint baseVertex, GLsizei count, GLsizei instanceCount); static void MAGNUM_GL_LOCAL drawElementsBaseVertexImplementationANGLE(GLenum mode, GLsizei count, GLenum type, const void* indices, GLint baseVertex);
void MAGNUM_GL_LOCAL drawArraysInstancedImplementationNV(GLint baseVertex, GLsizei count, GLsizei instanceCount); #endif
static void MAGNUM_GL_LOCAL drawElementsBaseVertexImplementationAssert(GLenum, GLsizei, GLenum, const void*, GLint);
#endif #endif
void MAGNUM_GL_LOCAL drawElementsInstancedImplementationANGLE(GLsizei count, GLintptr indexOffset, GLsizei instanceCount); #ifndef MAGNUM_TARGET_GLES2
#ifndef MAGNUM_TARGET_WEBGL #if defined(MAGNUM_TARGET_WEBGL) && __EMSCRIPTEN_major__*10000 + __EMSCRIPTEN_minor__*100 + __EMSCRIPTEN_tiny__ >= 13915
void MAGNUM_GL_LOCAL drawElementsInstancedImplementationEXT(GLsizei count, GLintptr indexOffset, GLsizei instanceCount); static void MAGNUM_GL_LOCAL drawRangeElementsBaseVertexImplementationANGLE(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices, GLint baseVertex);
void MAGNUM_GL_LOCAL drawElementsInstancedImplementationNV(GLsizei count, GLintptr indexOffset, GLsizei instanceCount); #endif
static void MAGNUM_GL_LOCAL drawRangeElementsBaseVertexImplementationAssert(GLenum, GLuint, GLuint, GLsizei, GLenum, const void*, GLint);
static void MAGNUM_GL_LOCAL drawArraysInstancedBaseInstanceImplementationAssert(GLenum, GLint, GLsizei, GLsizei, GLuint);
#if !defined(MAGNUM_TARGET_WEBGL) || __EMSCRIPTEN_major__*10000 + __EMSCRIPTEN_minor__*100 + __EMSCRIPTEN_tiny__ >= 13915
static void MAGNUM_GL_LOCAL drawElementsInstancedBaseInstanceImplementationANGLE(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instanceCount, GLuint baseInstance);
#endif
static void MAGNUM_GL_LOCAL drawElementsInstancedBaseInstanceImplementationAssert(GLenum, GLsizei, GLenum, const void*, GLsizei, GLuint);
static void MAGNUM_GL_LOCAL drawElementsInstancedBaseVertexBaseInstanceImplementationAssert(GLenum, GLsizei, GLenum, const void*, GLsizei, GLint, GLuint);
#if defined(MAGNUM_TARGET_WEBGL) && __EMSCRIPTEN_major__*10000 + __EMSCRIPTEN_minor__*100 + __EMSCRIPTEN_tiny__ >= 13915
static void MAGNUM_GL_LOCAL drawElementsInstancedBaseVertexImplementationANGLE(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instanceCount, GLint baseVertex);
#endif
static void MAGNUM_GL_LOCAL drawElementsInstancedBaseVertexImplementationAssert(GLenum, GLsizei, GLenum, const void*, GLsizei, GLint);
#endif #endif
#endif #endif
@ -1158,10 +1187,8 @@ class MAGNUM_GL_EXPORT Mesh: public AbstractObject {
object is constructed using NoCreate). Also fits in the gap. */ object is constructed using NoCreate). Also fits in the gap. */
bool _constructed{}; bool _constructed{};
Int _count{}, _baseVertex{}, _instanceCount{1}; Int _count{}, _baseVertex{}, _instanceCount{1};
#ifndef MAGNUM_TARGET_GLES
UnsignedInt _baseInstance{};
#endif
#ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES2
UnsignedInt _baseInstance{};
UnsignedInt _indexStart{}, _indexEnd{}; UnsignedInt _indexStart{}, _indexEnd{};
#endif #endif
GLintptr _indexOffset{}; GLintptr _indexOffset{};

66
src/Magnum/GL/MeshView.cpp

@ -26,6 +26,7 @@
#include "MeshView.h" #include "MeshView.h"
#include <Corrade/Containers/Array.h> #include <Corrade/Containers/Array.h>
#include <Corrade/Containers/ArrayTuple.h>
#include <Corrade/Utility/Assert.h> #include <Corrade/Utility/Assert.h>
#include "Magnum/GL/AbstractShaderProgram.h" #include "Magnum/GL/AbstractShaderProgram.h"
@ -84,7 +85,6 @@ MeshView& MeshView::draw(AbstractShaderProgram&& shader, TransformFeedback& xfb,
#endif #endif
#endif #endif
#ifndef MAGNUM_TARGET_WEBGL
void MeshView::multiDrawImplementationDefault(Containers::ArrayView<const Containers::Reference<MeshView>> meshes) { void MeshView::multiDrawImplementationDefault(Containers::ArrayView<const Containers::Reference<MeshView>> meshes) {
CORRADE_INTERNAL_ASSERT(meshes.size()); CORRADE_INTERNAL_ASSERT(meshes.size());
@ -96,24 +96,16 @@ void MeshView::multiDrawImplementationDefault(Containers::ArrayView<const Contai
Containers::Array<GLint> baseVertex{meshes.size()}; Containers::Array<GLint> baseVertex{meshes.size()};
/* Gather the parameters */ /* Gather the parameters */
#ifndef MAGNUM_TARGET_GLES
bool hasBaseVertex = false; bool hasBaseVertex = false;
#endif
std::size_t i = 0; std::size_t i = 0;
for(MeshView& mesh: meshes) { for(MeshView& mesh: meshes) {
CORRADE_ASSERT(mesh._instanceCount == 1, "GL::MeshView::draw(): cannot draw multiple instanced meshes", ); CORRADE_ASSERT(mesh._instanceCount == 1, "GL::AbstractShaderProgram::draw(): cannot draw multiple instanced meshes", );
count[i] = mesh._count; count[i] = mesh._count;
indices[i] = reinterpret_cast<GLvoid*>(mesh._indexOffset); indices[i] = reinterpret_cast<GLvoid*>(mesh._indexOffset);
baseVertex[i] = mesh._baseVertex; baseVertex[i] = mesh._baseVertex;
if(mesh._baseVertex) { if(mesh._baseVertex) hasBaseVertex = true;
#ifndef MAGNUM_TARGET_GLES
hasBaseVertex = true;
#else
CORRADE_ASSERT(!original._indexBuffer.id(), "GL::MeshView::draw(): desktop OpenGL is required for base vertex specification in indexed meshes", );
#endif
}
++i; ++i;
} }
@ -123,33 +115,40 @@ void MeshView::multiDrawImplementationDefault(Containers::ArrayView<const Contai
/* Non-indexed meshes */ /* Non-indexed meshes */
if(!original._indexBuffer.id()) { if(!original._indexBuffer.id()) {
#ifndef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES
glMultiDrawArrays(GLenum(original._primitive), baseVertex, count, meshes.size()); glMultiDrawArrays
#else #else
glMultiDrawArraysEXT(GLenum(original._primitive), baseVertex, count, meshes.size()); state.multiDrawArraysImplementation
#endif #endif
(GLenum(original._primitive), baseVertex, count, meshes.size());
/* Indexed meshes */ /* Indexed meshes */
} else { } else {
/* Indexed meshes with base vertex */ /* Indexed meshes with base vertex */
#ifndef MAGNUM_TARGET_GLES
if(hasBaseVertex) { if(hasBaseVertex) {
glMultiDrawElementsBaseVertex(GLenum(original._primitive), count, GLenum(original._indexType), indices, meshes.size(), baseVertex); #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2))
#ifndef MAGNUM_TARGET_GLES
glMultiDrawElementsBaseVertex
#else
state.multiDrawElementsBaseVertexImplementation
#endif
(GLenum(original._primitive), count, GLenum(original._indexType), indices, meshes.size(), baseVertex);
#else
CORRADE_ASSERT_UNREACHABLE("GL::AbstractShaderProgram::draw(): indexed mesh multi-draw with base vertex specification possible only since WebGL 2.0", );
#endif
/* Indexed meshes */ /* Indexed meshes */
} else } else {
#endif
{
#ifndef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES
glMultiDrawElements(GLenum(original._primitive), count, GLenum(original._indexType), indices, meshes.size()); glMultiDrawElements
#else #else
glMultiDrawElementsEXT(GLenum(original._primitive), count, GLenum(original._indexType), indices, meshes.size()); state.multiDrawElementsImplementation
#endif #endif
(GLenum(original._primitive), count, GLenum(original._indexType), indices, meshes.size());
} }
} }
(original.*state.unbindImplementation)(); (original.*state.unbindImplementation)();
} }
#endif
#ifdef MAGNUM_TARGET_GLES #ifdef MAGNUM_TARGET_GLES
void MeshView::multiDrawImplementationFallback(Containers::ArrayView<const Containers::Reference<MeshView>> meshes) { void MeshView::multiDrawImplementationFallback(Containers::ArrayView<const Containers::Reference<MeshView>> meshes) {
@ -157,10 +156,10 @@ void MeshView::multiDrawImplementationFallback(Containers::ArrayView<const Conta
/* Nothing to draw in this mesh */ /* Nothing to draw in this mesh */
if(!mesh._count) continue; if(!mesh._count) continue;
CORRADE_ASSERT(mesh._instanceCount == 1, "GL::MeshView::draw(): cannot draw multiple instanced meshes", ); CORRADE_ASSERT(mesh._instanceCount == 1, "GL::AbstractShaderProgram::draw(): cannot draw multiple instanced meshes", );
#ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES2
mesh._original.get().drawInternal(mesh._count, mesh._baseVertex, 1, mesh._indexOffset, mesh._indexStart, mesh._indexEnd); mesh._original.get().drawInternal(mesh._count, mesh._baseVertex, 1, mesh._baseInstance, mesh._indexOffset, mesh._indexStart, mesh._indexEnd);
#else #else
mesh._original.get().drawInternal(mesh._count, mesh._baseVertex, 1, mesh._indexOffset); mesh._original.get().drawInternal(mesh._count, mesh._baseVertex, 1, mesh._indexOffset);
#endif #endif
@ -168,4 +167,25 @@ void MeshView::multiDrawImplementationFallback(Containers::ArrayView<const Conta
} }
#endif #endif
#ifdef MAGNUM_TARGET_GLES
#if defined(MAGNUM_TARGET_WEBGL) && !defined(MAGNUM_TARGET_GLES2) && __EMSCRIPTEN_major__*10000 + __EMSCRIPTEN_minor__*100 + __EMSCRIPTEN_tiny__ >= 20005
void MeshView::multiDrawElementsBaseVertexImplementationANGLE(const GLenum mode, const GLsizei* const count, const GLenum type, const void* const* const indices, const GLsizei drawCount, const GLint* const baseVertex) {
/** @todo merge with the allocation in multiDrawImplementationDefault */
Containers::ArrayView<GLsizei> instanceCount;
Containers::ArrayView<GLuint> baseInstance;
Containers::ArrayTuple data{
{Containers::NoInit, std::size_t(drawCount), instanceCount},
{Containers::ValueInit, std::size_t(drawCount), baseInstance},
};
for(GLsizei& i: instanceCount) i = 1;
glMultiDrawElementsInstancedBaseVertexBaseInstanceANGLE(mode, count, type, indices, instanceCount, baseVertex, baseInstance, drawCount);
}
#endif
void MeshView::multiDrawElementsBaseVertexImplementationAssert(GLenum, const GLsizei*, GLenum, const void* const*, GLsizei, const GLint*) {
CORRADE_ASSERT_UNREACHABLE("GL::AbstractShaderProgram::draw(): no extension available for indexed mesh multi-draw with base vertex specification", );
}
#endif
}} }}

29
src/Magnum/GL/MeshView.h

@ -134,8 +134,12 @@ class MAGNUM_GL_EXPORT MeshView {
* @cpp 0 @ce. * @cpp 0 @ce.
* @requires_gl32 Extension @gl_extension{ARB,draw_elements_base_vertex} * @requires_gl32 Extension @gl_extension{ARB,draw_elements_base_vertex}
* for indexed meshes * for indexed meshes
* @requires_gles32 Base vertex cannot be specified for indexed meshes * @requires_es_extension Extension @gl_extension{OES,draw_elements_base_vertex}
* in OpenGL ES 3.1 or WebGL. * or @gl_extension{EXT,draw_elements_base_vertex} for indexed
* meshes on OpenGL ES 3.1 and older
* @requires_webgl_extension WebGL 2.0 and extension
* @webgl_extension{WEBGL,draw_instanced_base_vertex_base_instance}
* for indexed meshes
*/ */
MeshView& setBaseVertex(Int baseVertex) { MeshView& setBaseVertex(Int baseVertex) {
_baseVertex = baseVertex; _baseVertex = baseVertex;
@ -201,7 +205,7 @@ class MAGNUM_GL_EXPORT MeshView {
return *this; return *this;
} }
#ifndef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES2
/** @brief Base instance */ /** @brief Base instance */
UnsignedInt baseInstance() const { return _baseInstance; } UnsignedInt baseInstance() const { return _baseInstance; }
@ -212,8 +216,10 @@ class MAGNUM_GL_EXPORT MeshView {
* Ignored when calling @ref AbstractShaderProgram::drawTransformFeedback(). * Ignored when calling @ref AbstractShaderProgram::drawTransformFeedback().
* Default is @cpp 0 @ce. * Default is @cpp 0 @ce.
* @requires_gl42 Extension @gl_extension{ARB,base_instance} * @requires_gl42 Extension @gl_extension{ARB,base_instance}
* @requires_gl Base instance cannot be specified in OpenGL ES or * @requires_es_extension OpenGL ES 3.1 and extension
* WebGL. * @m_class{m-doc-external} [ANGLE_base_vertex_base_instance](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_base_vertex_base_instance.txt)
* @requires_webgl_extension WebGL 2.0 and extension
* @webgl_extension{WEBGL,draw_instanced_base_vertex_base_instance}
*/ */
MeshView& setBaseInstance(UnsignedInt baseInstance) { MeshView& setBaseInstance(UnsignedInt baseInstance) {
_baseInstance = baseInstance; _baseInstance = baseInstance;
@ -257,16 +263,23 @@ class MAGNUM_GL_EXPORT MeshView {
friend AbstractShaderProgram; friend AbstractShaderProgram;
friend Implementation::MeshState; friend Implementation::MeshState;
#ifndef MAGNUM_TARGET_WEBGL
static MAGNUM_GL_LOCAL void multiDrawImplementationDefault(Containers::ArrayView<const Containers::Reference<MeshView>> meshes); static MAGNUM_GL_LOCAL void multiDrawImplementationDefault(Containers::ArrayView<const Containers::Reference<MeshView>> meshes);
#endif #ifdef MAGNUM_TARGET_GLES
static MAGNUM_GL_LOCAL void multiDrawImplementationFallback(Containers::ArrayView<const Containers::Reference<MeshView>> meshes); static MAGNUM_GL_LOCAL void multiDrawImplementationFallback(Containers::ArrayView<const Containers::Reference<MeshView>> meshes);
#endif
#ifdef MAGNUM_TARGET_GLES
#if defined(MAGNUM_TARGET_WEBGL) && !defined(MAGNUM_TARGET_GLES2) && __EMSCRIPTEN_major__*10000 + __EMSCRIPTEN_minor__*100 + __EMSCRIPTEN_tiny__ >= 20005
static MAGNUM_GL_LOCAL void multiDrawElementsBaseVertexImplementationANGLE(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices, GLsizei drawCount, const GLint* baseVertex);
#endif
static MAGNUM_GL_LOCAL void multiDrawElementsBaseVertexImplementationAssert(GLenum, const GLsizei*, GLenum, const void* const*, GLsizei, const GLint*);
#endif
Containers::Reference<Mesh> _original; Containers::Reference<Mesh> _original;
bool _countSet{}; bool _countSet{};
Int _count{}, _baseVertex{}, _instanceCount{1}; Int _count{}, _baseVertex{}, _instanceCount{1};
#ifndef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES2
UnsignedInt _baseInstance{}; UnsignedInt _baseInstance{};
#endif #endif
GLintptr _indexOffset{}; GLintptr _indexOffset{};

21
src/Magnum/GL/Shader.cpp

@ -48,11 +48,6 @@ typedef char GLchar;
namespace Magnum { namespace GL { namespace Magnum { namespace GL {
namespace Implementation {
/* defined in Implementation/driverSpecific.cpp */
bool isShaderCompilationLogEmpty(const std::string& result);
}
namespace { namespace {
std::string shaderName(const Shader::Type type) { std::string shaderName(const Shader::Type type) {
@ -786,12 +781,16 @@ bool Shader::compile(std::initializer_list<Containers::Reference<Shader>> shader
glGetShaderiv(shader._id, GL_INFO_LOG_LENGTH, &logLength); glGetShaderiv(shader._id, GL_INFO_LOG_LENGTH, &logLength);
/* Error or warning message. The string is returned null-terminated, /* Error or warning message. The string is returned null-terminated,
scrap the \0 at the end afterwards */ strip the \0 at the end afterwards. */
std::string message(logLength, '\0'); std::string message(logLength, '\0');
if(message.size() > 1) if(message.size() > 1)
glGetShaderInfoLog(shader._id, message.size(), nullptr, &message[0]); glGetShaderInfoLog(shader._id, message.size(), nullptr, &message[0]);
message.resize(Math::max(logLength, 1)-1); message.resize(Math::max(logLength, 1)-1);
/* Some drivers are chatty and can't keep shut when there's nothing to
be said, handle that as well. */
Context::current().state().shader->cleanLogImplementation(message);
/* Show error log */ /* Show error log */
if(!success) { if(!success) {
Error out{Debug::Flag::NoNewlineAtTheEnd}; Error out{Debug::Flag::NoNewlineAtTheEnd};
@ -800,7 +799,7 @@ bool Shader::compile(std::initializer_list<Containers::Reference<Shader>> shader
out << "failed with the following message:" << Debug::newline << message; out << "failed with the following message:" << Debug::newline << message;
/* Or just warnings, if any */ /* Or just warnings, if any */
} else if(!message.empty() && !Implementation::isShaderCompilationLogEmpty(message)) { } else if(!message.empty()) {
Warning out{Debug::Flag::NoNewlineAtTheEnd}; Warning out{Debug::Flag::NoNewlineAtTheEnd};
out << "GL::Shader::compile(): compilation of" << shaderName(shader._type) << "shader"; out << "GL::Shader::compile(): compilation of" << shaderName(shader._type) << "shader";
if(shaders.size() != 1) out << i; if(shaders.size() != 1) out << i;
@ -815,6 +814,14 @@ bool Shader::compile(std::initializer_list<Containers::Reference<Shader>> shader
return allSuccess; return allSuccess;
} }
void Shader::cleanLogImplementationNoOp(std::string&) {}
#if defined(CORRADE_TARGET_WINDOWS) && !defined(MAGNUM_TARGET_GLES)
void Shader::cleanLogImplementationIntelWindows(std::string& message) {
if(message == "No errors.\n") message = {};
}
#endif
#ifndef DOXYGEN_GENERATING_OUTPUT #ifndef DOXYGEN_GENERATING_OUTPUT
Debug& operator<<(Debug& debug, const Shader::Type value) { Debug& operator<<(Debug& debug, const Shader::Type value) {
debug << "GL::Shader::Type" << Debug::nospace; debug << "GL::Shader::Type" << Debug::nospace;

5
src/Magnum/GL/Shader.h

@ -652,6 +652,11 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject {
void MAGNUM_GL_LOCAL addSourceImplementationEmscriptenPthread(std::string source); void MAGNUM_GL_LOCAL addSourceImplementationEmscriptenPthread(std::string source);
#endif #endif
static MAGNUM_GL_LOCAL void cleanLogImplementationNoOp(std::string& message);
#if defined(CORRADE_TARGET_WINDOWS) && !defined(MAGNUM_TARGET_GLES)
static MAGNUM_GL_LOCAL void cleanLogImplementationIntelWindows(std::string& message);
#endif
Type _type; Type _type;
GLuint _id; GLuint _id;

486
src/Magnum/GL/Test/MeshGLTest.cpp

@ -137,16 +137,38 @@ struct MeshGLTest: OpenGLTester {
void unbindVAOBeforeEnteringExternalSection(); void unbindVAOBeforeEnteringExternalSection();
void bindScratchVaoWhenEnteringExternalSection(); void bindScratchVaoWhenEnteringExternalSection();
#ifndef MAGNUM_TARGET_GLES #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2))
void setBaseVertex(); void setBaseVertex();
#endif #endif
#ifdef MAGNUM_TARGET_GLES
void setBaseVertexNoExtensionAvailable();
void setBaseVertexRangeNoExtensionAvailable();
#endif
void setInstanceCount(); void setInstanceCount();
void setInstanceCountIndexed(); #ifndef MAGNUM_TARGET_GLES2
#ifndef MAGNUM_TARGET_GLES
void setInstanceCountBaseInstance(); void setInstanceCountBaseInstance();
void setInstanceCountBaseInstanceIndexed(); #ifdef MAGNUM_TARGET_GLES
void setInstanceCountBaseVertex(); void setInstanceCountBaseInstanceNoExtensionAvailable();
void setInstanceCountBaseVertexBaseInstance(); #endif
#endif
void setInstanceCountIndexed();
#ifndef MAGNUM_TARGET_GLES2
void setInstanceCountIndexedBaseInstance();
#ifdef MAGNUM_TARGET_GLES
void setInstanceCountIndexedBaseInstanceNoExtensionAvailable();
#endif
#endif
#ifndef MAGNUM_TARGET_GLES2
void setInstanceCountIndexedBaseVertex();
#endif
#ifdef MAGNUM_TARGET_GLES
void setInstanceCountIndexedBaseVertexNoExtensionAvailable();
#endif
#ifndef MAGNUM_TARGET_GLES2
void setInstanceCountIndexedBaseVertexBaseInstance();
#ifdef MAGNUM_TARGET_GLES
void setInstanceCountIndexedBaseVertexBaseInstanceNoExtensionAvailable();
#endif
#endif #endif
void addVertexBufferInstancedFloat(); void addVertexBufferInstancedFloat();
@ -160,9 +182,13 @@ struct MeshGLTest: OpenGLTester {
void multiDraw(); void multiDraw();
void multiDrawIndexed(); void multiDrawIndexed();
#ifndef MAGNUM_TARGET_GLES void multiDrawInstanced();
#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2))
void multiDrawBaseVertex(); void multiDrawBaseVertex();
#endif #endif
#ifdef MAGNUM_TARGET_GLES
void multiDrawBaseVertexNoExtensionAvailable();
#endif
}; };
MeshGLTest::MeshGLTest() { MeshGLTest::MeshGLTest() {
@ -259,16 +285,38 @@ MeshGLTest::MeshGLTest() {
&MeshGLTest::unbindVAOBeforeEnteringExternalSection, &MeshGLTest::unbindVAOBeforeEnteringExternalSection,
&MeshGLTest::bindScratchVaoWhenEnteringExternalSection, &MeshGLTest::bindScratchVaoWhenEnteringExternalSection,
#ifndef MAGNUM_TARGET_GLES #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2))
&MeshGLTest::setBaseVertex, &MeshGLTest::setBaseVertex,
#endif #endif
#ifdef MAGNUM_TARGET_GLES
&MeshGLTest::setBaseVertexNoExtensionAvailable,
&MeshGLTest::setBaseVertexRangeNoExtensionAvailable,
#endif
&MeshGLTest::setInstanceCount, &MeshGLTest::setInstanceCount,
&MeshGLTest::setInstanceCountIndexed, #ifndef MAGNUM_TARGET_GLES2
#ifndef MAGNUM_TARGET_GLES
&MeshGLTest::setInstanceCountBaseInstance, &MeshGLTest::setInstanceCountBaseInstance,
&MeshGLTest::setInstanceCountBaseInstanceIndexed, #ifdef MAGNUM_TARGET_GLES
&MeshGLTest::setInstanceCountBaseVertex, &MeshGLTest::setInstanceCountBaseInstanceNoExtensionAvailable,
&MeshGLTest::setInstanceCountBaseVertexBaseInstance, #endif
#endif
&MeshGLTest::setInstanceCountIndexed,
#ifndef MAGNUM_TARGET_GLES2
&MeshGLTest::setInstanceCountIndexedBaseInstance,
#ifdef MAGNUM_TARGET_GLES
&MeshGLTest::setInstanceCountIndexedBaseInstanceNoExtensionAvailable,
#endif
#endif
#ifndef MAGNUM_TARGET_GLES2
&MeshGLTest::setInstanceCountIndexedBaseVertex,
#endif
#ifdef MAGNUM_TARGET_GLES
&MeshGLTest::setInstanceCountIndexedBaseVertexNoExtensionAvailable,
#endif
#ifndef MAGNUM_TARGET_GLES2
&MeshGLTest::setInstanceCountIndexedBaseVertexBaseInstance,
#ifdef MAGNUM_TARGET_GLES
&MeshGLTest::setInstanceCountIndexedBaseVertexBaseInstanceNoExtensionAvailable,
#endif
#endif #endif
&MeshGLTest::addVertexBufferInstancedFloat, &MeshGLTest::addVertexBufferInstancedFloat,
@ -282,8 +330,12 @@ MeshGLTest::MeshGLTest() {
&MeshGLTest::multiDraw, &MeshGLTest::multiDraw,
&MeshGLTest::multiDrawIndexed, &MeshGLTest::multiDrawIndexed,
#ifndef MAGNUM_TARGET_GLES &MeshGLTest::multiDrawInstanced,
&MeshGLTest::multiDrawBaseVertex #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2))
&MeshGLTest::multiDrawBaseVertex,
#endif
#ifdef MAGNUM_TARGET_GLES
&MeshGLTest::multiDrawBaseVertexNoExtensionAvailable
#endif #endif
}); });
} }
@ -1966,7 +2018,6 @@ const Float indexedVertexData[] = {
1.0f, -0.5f 1.0f, -0.5f
}; };
#ifndef MAGNUM_TARGET_GLES
const Float indexedVertexDataBaseVertex[] = { const Float indexedVertexDataBaseVertex[] = {
0.0f, 0.0f, /* Offset */ 0.0f, 0.0f, /* Offset */
@ -1995,7 +2046,6 @@ const Float indexedVertexDataBaseVertex[] = {
0.4f, 0.0f, -0.9f, 0.4f, 0.0f, -0.9f,
1.0f, -0.5f 1.0f, -0.5f
}; };
#endif
#ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES2
constexpr Color4ub indexedResult{64 + 15 + 97, 17 + 164 + 28, 56 + 17, 255}; constexpr Color4ub indexedResult{64 + 15 + 97, 17 + 164 + 28, 56 + 17, 255};
@ -2485,10 +2535,19 @@ void MeshGLTest::bindScratchVaoWhenEnteringExternalSection() {
#endif #endif
} }
#ifndef MAGNUM_TARGET_GLES #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2))
void MeshGLTest::setBaseVertex() { void MeshGLTest::setBaseVertex() {
#ifndef MAGNUM_TARGET_GLES
if(!Context::current().isExtensionSupported<Extensions::ARB::draw_elements_base_vertex>()) if(!Context::current().isExtensionSupported<Extensions::ARB::draw_elements_base_vertex>())
CORRADE_SKIP(Extensions::ARB::draw_elements_base_vertex::string() + std::string(" is not available.")); CORRADE_SKIP(Extensions::ARB::draw_elements_base_vertex::string() + std::string(" is not available."));
#elif !defined(MAGNUM_TARGET_WEBGL)
if(!Context::current().isExtensionSupported<Extensions::OES::draw_elements_base_vertex>() &&
!Context::current().isExtensionSupported<Extensions::EXT::draw_elements_base_vertex>())
CORRADE_SKIP(std::string{"Neither "} + Extensions::OES::draw_elements_base_vertex::string() + " nor " + Extensions::EXT::draw_elements_base_vertex::string() + " is available.");
#else
if(!Context::current().isExtensionSupported<Extensions::WEBGL::draw_instanced_base_vertex_base_instance>())
CORRADE_SKIP(Extensions::WEBGL::draw_instanced_base_vertex_base_instance::string() + std::string{" is not available."});
#endif
Buffer vertices; Buffer vertices;
vertices.setData(indexedVertexDataBaseVertex, BufferUsage::StaticDraw); vertices.setData(indexedVertexDataBaseVertex, BufferUsage::StaticDraw);
@ -2513,6 +2572,72 @@ void MeshGLTest::setBaseVertex() {
} }
#endif #endif
#ifdef MAGNUM_TARGET_GLES
void MeshGLTest::setBaseVertexNoExtensionAvailable() {
#ifndef MAGNUM_TARGET_WEBGL
if(Context::current().isVersionSupported(Version::GLES320))
CORRADE_SKIP("OpenGL ES 3.2 is available.");
if(Context::current().isExtensionSupported<Extensions::EXT::draw_elements_base_vertex>())
CORRADE_SKIP(Extensions::EXT::draw_elements_base_vertex::string() + std::string{" is available."});
if(Context::current().isExtensionSupported<Extensions::OES::draw_elements_base_vertex>())
CORRADE_SKIP(Extensions::OES::draw_elements_base_vertex::string() + std::string{" is available."});
#elif !defined(MAGNUM_TARGET_GLES2)
if(Context::current().isExtensionSupported<Extensions::WEBGL::draw_instanced_base_vertex_base_instance>())
CORRADE_SKIP(Extensions::WEBGL::draw_instanced_base_vertex_base_instance::string() + std::string{" is available."});
#endif
constexpr UnsignedShort indexData[] = { 2, 1, 0 };
Buffer indices{Buffer::TargetHint::ElementArray};
indices.setData(indexData, BufferUsage::StaticDraw);
Mesh mesh;
mesh.setCount(3)
.setBaseVertex(1)
.setIndexBuffer(indices, 0, MeshIndexType::UnsignedShort);
std::ostringstream out;
Error redirectError{&out};
MultipleShader{}.draw(mesh);
#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2))
CORRADE_COMPARE(out.str(), "GL::AbstractShaderProgram::draw(): no extension available for indexed mesh draw with base vertex specification\n");
#else
CORRADE_COMPARE(out.str(), "GL::AbstractShaderProgram::draw(): indexed mesh draw with base vertex specification possible only since WebGL 2.0\n");
#endif
}
void MeshGLTest::setBaseVertexRangeNoExtensionAvailable() {
#ifndef MAGNUM_TARGET_WEBGL
if(Context::current().isVersionSupported(Version::GLES320))
CORRADE_SKIP("OpenGL ES 3.2 is available.");
if(Context::current().isExtensionSupported<Extensions::EXT::draw_elements_base_vertex>())
CORRADE_SKIP(Extensions::EXT::draw_elements_base_vertex::string() + std::string{" is available."});
if(Context::current().isExtensionSupported<Extensions::OES::draw_elements_base_vertex>())
CORRADE_SKIP(Extensions::OES::draw_elements_base_vertex::string() + std::string{" is available."});
#elif !defined(MAGNUM_TARGET_GLES2)
if(Context::current().isExtensionSupported<Extensions::WEBGL::draw_instanced_base_vertex_base_instance>())
CORRADE_SKIP(Extensions::WEBGL::draw_instanced_base_vertex_base_instance::string() + std::string{" is available."});
#endif
constexpr UnsignedShort indexData[] = { 2, 1, 0 };
Buffer indices{Buffer::TargetHint::ElementArray};
indices.setData(indexData, BufferUsage::StaticDraw);
Mesh mesh;
mesh.setCount(3)
.setBaseVertex(1)
.setIndexBuffer(indices, 0, MeshIndexType::UnsignedShort, 0, 2);
std::ostringstream out;
Error redirectError{&out};
MultipleShader{}.draw(mesh);
#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2))
CORRADE_COMPARE(out.str(), "GL::AbstractShaderProgram::draw(): no extension available for indexed mesh draw with base vertex specification\n");
#else
CORRADE_COMPARE(out.str(), "GL::AbstractShaderProgram::draw(): indexed mesh draw with base vertex specification possible only since WebGL 2.0\n");
#endif
}
#endif
void MeshGLTest::setInstanceCount() { void MeshGLTest::setInstanceCount() {
/* Verbatim copy of addVertexBufferFloat() with added extension check and /* Verbatim copy of addVertexBufferFloat() with added extension check and
setInstanceCount() call. It would just render three times the same setInstanceCount() call. It would just render three times the same
@ -2561,6 +2686,71 @@ void MeshGLTest::setInstanceCount() {
CORRADE_COMPARE(value, 96); CORRADE_COMPARE(value, 96);
} }
#ifndef MAGNUM_TARGET_GLES2
void MeshGLTest::setInstanceCountBaseInstance() {
/* Verbatim copy of setInstanceCount() with additional extension check and
setBaseInstance() call. It would just render three times the same
value. I'm too lazy to invent proper test case, so I'll just check that
it didn't generate any error and rendered something */
#ifndef MAGNUM_TARGET_GLES
if(!Context::current().isExtensionSupported<Extensions::ARB::draw_instanced>())
CORRADE_SKIP(Extensions::ARB::draw_instanced::string() + std::string(" is not available."));
if(!Context::current().isExtensionSupported<Extensions::ARB::base_instance>())
CORRADE_SKIP(Extensions::ARB::base_instance::string() + std::string(" is not available."));
#elif !defined(MAGNUM_TARGET_WEBGL)
if(!Context::current().isExtensionSupported<Extensions::ANGLE::base_vertex_base_instance>())
CORRADE_SKIP(Extensions::ANGLE::base_vertex_base_instance::string() + std::string{" is not available."});
#else
if(!Context::current().isExtensionSupported<Extensions::WEBGL::draw_instanced_base_vertex_base_instance>())
CORRADE_SKIP(Extensions::WEBGL::draw_instanced_base_vertex_base_instance::string() + std::string{" is not available."});
#endif
typedef Attribute<0, Float> Attribute;
const Float data[] = { 0.0f, -0.7f, Math::unpack<Float, UnsignedByte>(96) };
Buffer buffer;
buffer.setData(data, BufferUsage::StaticDraw);
Mesh mesh;
mesh.setBaseVertex(1)
.setInstanceCount(3)
.setBaseInstance(72)
.addVertexBuffer(buffer, 4, Attribute());
MAGNUM_VERIFY_NO_GL_ERROR();
const auto value = Checker(FloatShader("float", "vec4(valueInterpolated, 0.0, 0.0, 0.0)"),
RenderbufferFormat::RGBA8,
mesh).get<UnsignedByte>(PixelFormat::RGBA, PixelType::UnsignedByte);
MAGNUM_VERIFY_NO_GL_ERROR();
CORRADE_COMPARE(value, 96);
}
#ifdef MAGNUM_TARGET_GLES
void MeshGLTest::setInstanceCountBaseInstanceNoExtensionAvailable() {
#ifndef MAGNUM_TARGET_WEBGL
if(Context::current().isExtensionSupported<Extensions::ANGLE::base_vertex_base_instance>())
CORRADE_SKIP(Extensions::ANGLE::base_vertex_base_instance::string() + std::string{" is available."});
#else
if(Context::current().isExtensionSupported<Extensions::WEBGL::draw_instanced_base_vertex_base_instance>())
CORRADE_SKIP(Extensions::WEBGL::draw_instanced_base_vertex_base_instance::string() + std::string{" is available."});
#endif
Mesh mesh;
mesh.setCount(3)
.setInstanceCount(2)
.setBaseInstance(1);
std::ostringstream out;
Error redirectError{&out};
MultipleShader{}.draw(mesh);
CORRADE_COMPARE(out.str(), "GL::AbstractShaderProgram::draw(): no extension available for instanced mesh draw with base instance specification\n");
}
#endif
#endif
void MeshGLTest::setInstanceCountIndexed() { void MeshGLTest::setInstanceCountIndexed() {
/* Verbatim copy of setIndexBuffer() with added extension check and /* Verbatim copy of setIndexBuffer() with added extension check and
setInstanceCount() call. It would just render three times the same setInstanceCount() call. It would just render three times the same
@ -2611,50 +2801,25 @@ void MeshGLTest::setInstanceCountIndexed() {
CORRADE_COMPARE(value, indexedResult); CORRADE_COMPARE(value, indexedResult);
} }
#ifndef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES2
void MeshGLTest::setInstanceCountBaseInstance() { void MeshGLTest::setInstanceCountIndexedBaseInstance() {
/* Verbatim copy of setInstanceCount() with additional extension check and
setBaseInstance() call. It would just render three times the same
value. I'm too lazy to invent proper test case, so I'll just check that
it didn't generate any error and rendered something */
if(!Context::current().isExtensionSupported<Extensions::ARB::draw_instanced>())
CORRADE_SKIP(Extensions::ARB::draw_instanced::string() + std::string(" is not available."));
if(!Context::current().isExtensionSupported<Extensions::ARB::base_instance>())
CORRADE_SKIP(Extensions::ARB::base_instance::string() + std::string(" is not available."));
typedef Attribute<0, Float> Attribute;
const Float data[] = { 0.0f, -0.7f, Math::unpack<Float, UnsignedByte>(96) };
Buffer buffer;
buffer.setData(data, BufferUsage::StaticDraw);
Mesh mesh;
mesh.setBaseVertex(1)
.setInstanceCount(3)
.setBaseInstance(72)
.addVertexBuffer(buffer, 4, Attribute());
MAGNUM_VERIFY_NO_GL_ERROR();
const auto value = Checker(FloatShader("float", "vec4(valueInterpolated, 0.0, 0.0, 0.0)"),
RenderbufferFormat::RGBA8,
mesh).get<UnsignedByte>(PixelFormat::RGBA, PixelType::UnsignedByte);
MAGNUM_VERIFY_NO_GL_ERROR();
CORRADE_COMPARE(value, 96);
}
void MeshGLTest::setInstanceCountBaseInstanceIndexed() {
/* Verbatim copy of setInstanceCountIndexed() with additional extension /* Verbatim copy of setInstanceCountIndexed() with additional extension
check and setBaseInstance() call. It would just render three times the check and setBaseInstance() call. It would just render three times the
same value. I'm too lazy to invent proper test case, so I'll just check same value. I'm too lazy to invent proper test case, so I'll just check
that it didn't generate any error and rendered something */ that it didn't generate any error and rendered something */
#ifndef MAGNUM_TARGET_GLES
if(!Context::current().isExtensionSupported<Extensions::ARB::draw_instanced>()) if(!Context::current().isExtensionSupported<Extensions::ARB::draw_instanced>())
CORRADE_SKIP(Extensions::ARB::draw_instanced::string() + std::string(" is not available.")); CORRADE_SKIP(Extensions::ARB::draw_instanced::string() + std::string(" is not available."));
if(!Context::current().isExtensionSupported<Extensions::ARB::base_instance>()) if(!Context::current().isExtensionSupported<Extensions::ARB::base_instance>())
CORRADE_SKIP(Extensions::ARB::base_instance::string() + std::string(" is not available.")); CORRADE_SKIP(Extensions::ARB::base_instance::string() + std::string(" is not available."));
#elif !defined(MAGNUM_TARGET_WEBGL)
if(!Context::current().isExtensionSupported<Extensions::ANGLE::base_vertex_base_instance>())
CORRADE_SKIP(Extensions::ANGLE::base_vertex_base_instance::string() + std::string{" is not available."});
#else
if(!Context::current().isExtensionSupported<Extensions::WEBGL::draw_instanced_base_vertex_base_instance>())
CORRADE_SKIP(Extensions::WEBGL::draw_instanced_base_vertex_base_instance::string() + std::string{" is not available."});
#endif
Buffer vertices; Buffer vertices;
vertices.setData(indexedVertexData, BufferUsage::StaticDraw); vertices.setData(indexedVertexData, BufferUsage::StaticDraw);
@ -2679,16 +2844,54 @@ void MeshGLTest::setInstanceCountBaseInstanceIndexed() {
CORRADE_COMPARE(value, indexedResult); CORRADE_COMPARE(value, indexedResult);
} }
void MeshGLTest::setInstanceCountBaseVertex() { #ifdef MAGNUM_TARGET_GLES
void MeshGLTest::setInstanceCountIndexedBaseInstanceNoExtensionAvailable() {
#ifndef MAGNUM_TARGET_WEBGL
if(Context::current().isExtensionSupported<Extensions::ANGLE::base_vertex_base_instance>())
CORRADE_SKIP(Extensions::ANGLE::base_vertex_base_instance::string() + std::string{" is available."});
#else
if(Context::current().isExtensionSupported<Extensions::WEBGL::draw_instanced_base_vertex_base_instance>())
CORRADE_SKIP(Extensions::WEBGL::draw_instanced_base_vertex_base_instance::string() + std::string{" is available."});
#endif
constexpr UnsignedShort indexData[] = { 2, 1, 0 };
Buffer indices{Buffer::TargetHint::ElementArray};
indices.setData(indexData, BufferUsage::StaticDraw);
Mesh mesh;
mesh.setCount(3)
.setInstanceCount(2)
.setBaseInstance(1)
.setIndexBuffer(indices, 0, MeshIndexType::UnsignedShort);
std::ostringstream out;
Error redirectError{&out};
MultipleShader{}.draw(mesh);
CORRADE_COMPARE(out.str(), "GL::AbstractShaderProgram::draw(): no extension available for instanced indexed mesh draw with base instance specification\n");
}
#endif
#endif
#ifndef MAGNUM_TARGET_GLES2
void MeshGLTest::setInstanceCountIndexedBaseVertex() {
/* Verbatim copy of setBaseVertex() with additional extension check and /* Verbatim copy of setBaseVertex() with additional extension check and
setInstanceCount() call. It would just render three times the same setInstanceCount() call. It would just render three times the same
value. I'm too lazy to invent proper test case, so I'll just check value. I'm too lazy to invent proper test case, so I'll just check
that it didn't generate any error and rendered something */ that it didn't generate any error and rendered something */
#ifndef MAGNUM_TARGET_GLES
if(!Context::current().isExtensionSupported<Extensions::ARB::draw_instanced>()) if(!Context::current().isExtensionSupported<Extensions::ARB::draw_instanced>())
CORRADE_SKIP(Extensions::ARB::draw_instanced::string() + std::string(" is not available.")); CORRADE_SKIP(Extensions::ARB::draw_instanced::string() + std::string(" is not available."));
if(!Context::current().isExtensionSupported<Extensions::ARB::draw_elements_base_vertex>()) if(!Context::current().isExtensionSupported<Extensions::ARB::draw_elements_base_vertex>())
CORRADE_SKIP(Extensions::ARB::draw_elements_base_vertex::string() + std::string(" is not available.")); CORRADE_SKIP(Extensions::ARB::draw_elements_base_vertex::string() + std::string(" is not available."));
#elif !defined(MAGNUM_TARGET_WEBGL)
if(!Context::current().isExtensionSupported<Extensions::OES::draw_elements_base_vertex>() &&
!Context::current().isExtensionSupported<Extensions::EXT::draw_elements_base_vertex>())
CORRADE_SKIP(std::string{"Neither "} + Extensions::OES::draw_elements_base_vertex::string() + " nor " + Extensions::EXT::draw_elements_base_vertex::string() + " is available.");
#else
if(!Context::current().isExtensionSupported<Extensions::WEBGL::draw_instanced_base_vertex_base_instance>())
CORRADE_SKIP(Extensions::WEBGL::draw_instanced_base_vertex_base_instance::string() + std::string{" is not available."});
#endif
Buffer vertices; Buffer vertices;
vertices.setData(indexedVertexDataBaseVertex, BufferUsage::StaticDraw); vertices.setData(indexedVertexDataBaseVertex, BufferUsage::StaticDraw);
@ -2712,19 +2915,66 @@ void MeshGLTest::setInstanceCountBaseVertex() {
MAGNUM_VERIFY_NO_GL_ERROR(); MAGNUM_VERIFY_NO_GL_ERROR();
CORRADE_COMPARE(value, indexedResult); CORRADE_COMPARE(value, indexedResult);
} }
#endif
#ifdef MAGNUM_TARGET_GLES
void MeshGLTest::setInstanceCountIndexedBaseVertexNoExtensionAvailable() {
#ifndef MAGNUM_TARGET_GLES2
#ifndef MAGNUM_TARGET_WEBGL
if(Context::current().isVersionSupported(Version::GLES320))
CORRADE_SKIP("OpenGL ES 3.2 is available.");
if(Context::current().isExtensionSupported<Extensions::EXT::draw_elements_base_vertex>())
CORRADE_SKIP(Extensions::EXT::draw_elements_base_vertex::string() + std::string{" is available."});
if(Context::current().isExtensionSupported<Extensions::OES::draw_elements_base_vertex>())
CORRADE_SKIP(Extensions::OES::draw_elements_base_vertex::string() + std::string{" is available."});
#else
if(Context::current().isExtensionSupported<Extensions::WEBGL::draw_instanced_base_vertex_base_instance>())
CORRADE_SKIP(Extensions::WEBGL::draw_instanced_base_vertex_base_instance::string() + std::string{" is available."});
#endif
#endif
constexpr UnsignedShort indexData[] = { 2, 1, 0 };
Buffer indices{Buffer::TargetHint::ElementArray};
indices.setData(indexData, BufferUsage::StaticDraw);
void MeshGLTest::setInstanceCountBaseVertexBaseInstance() { Mesh mesh;
mesh.setCount(3)
.setInstanceCount(2)
.setBaseVertex(1)
.setIndexBuffer(indices, 0, MeshIndexType::UnsignedShort);
std::ostringstream out;
Error redirectError{&out};
MultipleShader{}.draw(mesh);
#ifndef MAGNUM_TARGET_GLES2
CORRADE_COMPARE(out.str(), "GL::AbstractShaderProgram::draw(): no extension available for instanced indexed mesh draw with base vertex specification\n");
#else
CORRADE_COMPARE(out.str(), "GL::AbstractShaderProgram::draw(): instanced indexed mesh draw with base vertex specification possible only since OpenGL ES 3.0\n");
#endif
}
#endif
#ifndef MAGNUM_TARGET_GLES2
void MeshGLTest::setInstanceCountIndexedBaseVertexBaseInstance() {
/* Verbatim copy of setInstanceCountBaseVertex() with added extension check /* Verbatim copy of setInstanceCountBaseVertex() with added extension check
and setBaseInstance() call. It would just render three times the same and setBaseInstance() call. It would just render three times the same
value. I'm too lazy to invent proper test case, so I'll just check value. I'm too lazy to invent proper test case, so I'll just check
that it didn't generate any error and rendered something */ that it didn't generate any error and rendered something */
#ifndef MAGNUM_TARGET_GLES
if(!Context::current().isExtensionSupported<Extensions::ARB::draw_instanced>()) if(!Context::current().isExtensionSupported<Extensions::ARB::draw_instanced>())
CORRADE_SKIP(Extensions::ARB::draw_instanced::string() + std::string(" is not available.")); CORRADE_SKIP(Extensions::ARB::draw_instanced::string() + std::string(" is not available."));
if(!Context::current().isExtensionSupported<Extensions::ARB::draw_elements_base_vertex>()) if(!Context::current().isExtensionSupported<Extensions::ARB::draw_elements_base_vertex>())
CORRADE_SKIP(Extensions::ARB::draw_elements_base_vertex::string() + std::string(" is not available.")); CORRADE_SKIP(Extensions::ARB::draw_elements_base_vertex::string() + std::string(" is not available."));
if(!Context::current().isExtensionSupported<Extensions::ARB::base_instance>()) if(!Context::current().isExtensionSupported<Extensions::ARB::base_instance>())
CORRADE_SKIP(Extensions::ARB::base_instance::string() + std::string(" is not available.")); CORRADE_SKIP(Extensions::ARB::base_instance::string() + std::string(" is not available."));
#elif !defined(MAGNUM_TARGET_WEBGL)
if(!Context::current().isExtensionSupported<Extensions::ANGLE::base_vertex_base_instance>())
CORRADE_SKIP(Extensions::ANGLE::base_vertex_base_instance::string() + std::string{" is not available."});
#else
if(!Context::current().isExtensionSupported<Extensions::WEBGL::draw_instanced_base_vertex_base_instance>())
CORRADE_SKIP(Extensions::WEBGL::draw_instanced_base_vertex_base_instance::string() + std::string{" is not available."});
#endif
Buffer vertices; Buffer vertices;
vertices.setData(indexedVertexDataBaseVertex, BufferUsage::StaticDraw); vertices.setData(indexedVertexDataBaseVertex, BufferUsage::StaticDraw);
@ -2749,6 +2999,38 @@ void MeshGLTest::setInstanceCountBaseVertexBaseInstance() {
MAGNUM_VERIFY_NO_GL_ERROR(); MAGNUM_VERIFY_NO_GL_ERROR();
CORRADE_COMPARE(value, indexedResult); CORRADE_COMPARE(value, indexedResult);
} }
#ifdef MAGNUM_TARGET_GLES
void MeshGLTest::setInstanceCountIndexedBaseVertexBaseInstanceNoExtensionAvailable() {
#ifndef MAGNUM_TARGET_WEBGL
if(Context::current().isExtensionSupported<Extensions::ANGLE::base_vertex_base_instance>())
CORRADE_SKIP(Extensions::ANGLE::base_vertex_base_instance::string() + std::string{" is available."});
#else
if(Context::current().isExtensionSupported<Extensions::WEBGL::draw_instanced_base_vertex_base_instance>())
CORRADE_SKIP(Extensions::WEBGL::draw_instanced_base_vertex_base_instance::string() + std::string{" is available."});
#endif
constexpr UnsignedShort indexData[] = { 2, 1, 0 };
Buffer indices{Buffer::TargetHint::ElementArray};
indices.setData(indexData, BufferUsage::StaticDraw);
Mesh mesh;
mesh.setCount(3)
.setInstanceCount(2)
.setBaseVertex(1)
.setBaseInstance(1)
.setIndexBuffer(indices, 0, MeshIndexType::UnsignedShort);
std::ostringstream out;
Error redirectError{&out};
MultipleShader{}.draw(mesh);
#ifndef MAGNUM_TARGET_GLES2
CORRADE_COMPARE(out.str(), "GL::AbstractShaderProgram::draw(): no extension available for instanced indexed mesh draw with base vertex and base instance specification\n");
#else
CORRADE_COMPARE(out.str(), "GL::AbstractShaderProgram::draw(): instanced indexed mesh draw with base vertex specification possible only since OpenGL 3.0\n");
#endif
}
#endif
#endif #endif
void MeshGLTest::addVertexBufferInstancedFloat() { void MeshGLTest::addVertexBufferInstancedFloat() {
@ -3005,9 +3287,15 @@ template<class T> T MultiChecker::get(PixelFormat format, PixelType type) {
#endif #endif
void MeshGLTest::multiDraw() { void MeshGLTest::multiDraw() {
#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) #ifdef MAGNUM_TARGET_GLES
if(!Context::current().isExtensionSupported<Extensions::EXT::multi_draw_arrays>()) #ifndef MAGNUM_TARGET_WEBGL
Debug() << Extensions::EXT::multi_draw_arrays::string() << "not supported, using fallback implementation"; if(!Context::current().isExtensionSupported<Extensions::EXT::multi_draw_arrays>() &&
!Context::current().isExtensionSupported<Extensions::ANGLE::multi_draw>())
Debug{} << "Neither" << Extensions::EXT::multi_draw_arrays::string() << "nor" << Extensions::ANGLE::multi_draw::string() << "is supported, using fallback implementation";
#else
if(!Context::current().isExtensionSupported<Extensions::WEBGL::multi_draw>())
Debug{} << Extensions::WEBGL::multi_draw::string() << "is not supported, using fallback implementation";
#endif
#endif #endif
typedef Attribute<0, Float> Attribute; typedef Attribute<0, Float> Attribute;
@ -3042,9 +3330,15 @@ void MeshGLTest::multiDraw() {
} }
void MeshGLTest::multiDrawIndexed() { void MeshGLTest::multiDrawIndexed() {
#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) #ifdef MAGNUM_TARGET_GLES
if(!Context::current().isExtensionSupported<Extensions::EXT::multi_draw_arrays>()) #ifndef MAGNUM_TARGET_WEBGL
Debug() << Extensions::EXT::multi_draw_arrays::string() << "not supported, using fallback implementation"; if(!Context::current().isExtensionSupported<Extensions::EXT::multi_draw_arrays>() &&
!Context::current().isExtensionSupported<Extensions::ANGLE::multi_draw>())
Debug{} << "Neither" << Extensions::EXT::multi_draw_arrays::string() << "nor" << Extensions::ANGLE::multi_draw::string() << "is supported, using fallback implementation";
#else
if(!Context::current().isExtensionSupported<Extensions::WEBGL::multi_draw>())
Debug{} << Extensions::WEBGL::multi_draw::string() << "is not supported, using fallback implementation";
#endif
#endif #endif
Buffer vertices; Buffer vertices;
@ -3067,10 +3361,31 @@ void MeshGLTest::multiDrawIndexed() {
CORRADE_COMPARE(value, indexedResult); CORRADE_COMPARE(value, indexedResult);
} }
#ifndef MAGNUM_TARGET_GLES void MeshGLTest::multiDrawInstanced() {
Mesh mesh;
MeshView view{mesh};
view.setCount(3)
.setInstanceCount(2);
std::ostringstream out;
Error redirectError{&out};
MultipleShader{}.draw({view, view});
CORRADE_COMPARE(out.str(), "GL::AbstractShaderProgram::draw(): cannot draw multiple instanced meshes\n");
}
#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2))
void MeshGLTest::multiDrawBaseVertex() { void MeshGLTest::multiDrawBaseVertex() {
#ifndef MAGNUM_TARGET_GLES
if(!Context::current().isExtensionSupported<Extensions::ARB::draw_elements_base_vertex>()) if(!Context::current().isExtensionSupported<Extensions::ARB::draw_elements_base_vertex>())
CORRADE_SKIP(Extensions::ARB::draw_elements_base_vertex::string() + std::string(" is not available.")); CORRADE_SKIP(Extensions::ARB::draw_elements_base_vertex::string() + std::string(" is not available."));
#elif !defined(MAGNUM_TARGET_WEBGL)
if(!Context::current().isExtensionSupported<Extensions::OES::draw_elements_base_vertex>() &&
!Context::current().isExtensionSupported<Extensions::EXT::draw_elements_base_vertex>())
CORRADE_SKIP(std::string{"Neither "} + Extensions::OES::draw_elements_base_vertex::string() + " nor " + Extensions::EXT::draw_elements_base_vertex::string() + " is available.");
#else
if(!Context::current().isExtensionSupported<Extensions::WEBGL::multi_draw_instanced_base_vertex_base_instance>())
CORRADE_SKIP(Extensions::WEBGL::multi_draw_instanced_base_vertex_base_instance::string() + std::string{" is not available."});
#endif
Buffer vertices; Buffer vertices;
vertices.setData(indexedVertexDataBaseVertex, BufferUsage::StaticDraw); vertices.setData(indexedVertexDataBaseVertex, BufferUsage::StaticDraw);
@ -3094,6 +3409,53 @@ void MeshGLTest::multiDrawBaseVertex() {
} }
#endif #endif
#ifdef MAGNUM_TARGET_GLES
void MeshGLTest::multiDrawBaseVertexNoExtensionAvailable() {
#ifdef MAGNUM_TARGET_GLES
/* If the multidraw extensions aren't available, we can't test this assert,
only the assert in the fallback path, which is already tested above. */
#ifndef MAGNUM_TARGET_WEBGL
if(!Context::current().isExtensionSupported<Extensions::EXT::multi_draw_arrays>() &&
!Context::current().isExtensionSupported<Extensions::ANGLE::multi_draw>())
CORRADE_SKIP(std::string{"Neither "} + Extensions::EXT::multi_draw_arrays::string() + " nor " + Extensions::ANGLE::multi_draw::string() + " is available.");
#else
if(!Context::current().isExtensionSupported<Extensions::WEBGL::multi_draw>())
CORRADE_SKIP(Extensions::WEBGL::multi_draw::string() + std::string{" is not available."});
#endif
#endif
#ifndef MAGNUM_TARGET_WEBGL
if(Context::current().isExtensionSupported<Extensions::EXT::draw_elements_base_vertex>())
CORRADE_SKIP(Extensions::EXT::draw_elements_base_vertex::string() + std::string{" is available."});
if(Context::current().isExtensionSupported<Extensions::OES::draw_elements_base_vertex>())
CORRADE_SKIP(Extensions::OES::draw_elements_base_vertex::string() + std::string{" is available."});
#elif !defined(MAGNUM_TARGET_GLES2)
if(Context::current().isExtensionSupported<Extensions::WEBGL::multi_draw_instanced_base_vertex_base_instance>())
CORRADE_SKIP(Extensions::WEBGL::multi_draw_instanced_base_vertex_base_instance::string() + std::string{" is available."});
#endif
constexpr UnsignedShort indexData[] = { 2, 1, 0 };
Buffer indices{Buffer::TargetHint::ElementArray};
indices.setData(indexData, BufferUsage::StaticDraw);
Mesh mesh;
mesh.setIndexBuffer(indices, 0, MeshIndexType::UnsignedShort);
MeshView view{mesh};
view.setCount(3)
.setBaseVertex(1);
std::ostringstream out;
Error redirectError{&out};
MultipleShader{}.draw({view, view});
#if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2))
CORRADE_COMPARE(out.str(), "GL::AbstractShaderProgram::draw(): no extension available for indexed mesh multi-draw with base vertex specification\n");
#else
CORRADE_COMPARE(out.str(), "GL::AbstractShaderProgram::draw(): indexed mesh multi-draw with base vertex specification possible only since WebGL 2.0\n");
#endif
}
#endif
}}}} }}}}
CORRADE_TEST_MAIN(Magnum::GL::Test::MeshGLTest) CORRADE_TEST_MAIN(Magnum::GL::Test::MeshGLTest)

4
src/Magnum/GL/TextureFormat.cpp

@ -64,8 +64,10 @@ Debug& operator<<(Debug& debug, const TextureFormat value) {
_c(RGBA8) _c(RGBA8)
#endif #endif
#ifndef MAGNUM_TARGET_WEBGL #ifndef MAGNUM_TARGET_WEBGL
#ifndef MAGNUM_TARGET_GLES2
_c(SR8) _c(SR8)
#ifdef MAGNUM_TARGET_GLES #endif
#if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_GLES2)
_c(SRG8) _c(SRG8)
#endif #endif
#endif #endif

10
src/Magnum/GL/TextureFormat.h

@ -196,21 +196,25 @@ enum class TextureFormat: GLenum {
#endif #endif
#ifndef MAGNUM_TARGET_WEBGL #ifndef MAGNUM_TARGET_WEBGL
#ifndef MAGNUM_TARGET_GLES2
/** /**
* sRGB-encoded red component, normalized unsigned byte. * sRGB-encoded red component, normalized unsigned byte.
* @requires_extension Extension @gl_extension{EXT,texture_sRGB_R8} * @requires_extension Extension @gl_extension{EXT,texture_sRGB_R8}
* @requires_es_extension Extension @gl_extension{EXT,texture_sRGB_R8} * @requires_es_extension OpenGL ES 3.0 and extension
* @gl_extension{EXT,texture_sRGB_R8}
* @requires_gles One- and two-component sRGB texture formats are not * @requires_gles One- and two-component sRGB texture formats are not
* available in WebGL, use @ref TextureFormat::SRGB8 or * available in WebGL, use @ref TextureFormat::SRGB8 or
* @ref TextureFormat::SRGB8Alpha8 * @ref TextureFormat::SRGB8Alpha8
* @m_since{2019,10} * @m_since{2019,10}
*/ */
SR8 = GL_SR8_EXT, SR8 = GL_SR8_EXT,
#endif
#if defined(MAGNUM_TARGET_GLES) || defined(DOXYGEN_GENERATING_OUTPUT) #if (defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_GLES2)) || defined(DOXYGEN_GENERATING_OUTPUT)
/** /**
* sRGB-encoded red and green component, normalized unsigned byte. * sRGB-encoded red and green component, normalized unsigned byte.
* @requires_es_extension Extension @gl_extension{EXT,texture_sRGB_RG8} * @requires_es_extension OpenGL ES 3.0 and extension
* @gl_extension{EXT,texture_sRGB_RG8}
* @requires_gles One- and two-component sRGB texture formats are not * @requires_gles One- and two-component sRGB texture formats are not
* available in WebGL, use @ref TextureFormat::SRGB8 or * available in WebGL, use @ref TextureFormat::SRGB8 or
* @ref TextureFormat::SRGB8Alpha8 instead. Only * @ref TextureFormat::SRGB8Alpha8 instead. Only

24
src/Magnum/Mesh.h

@ -240,38 +240,38 @@ In case of OpenGL, corresponds to @ref GL::MeshIndexType and is convertible to
it using @ref GL::meshIndexType(). See documentation of each value for more it using @ref GL::meshIndexType(). See documentation of each value for more
information about the mapping. information about the mapping.
In case of Vulkan, corresponds to @type_vk_keyword{IndexType} and is In case of Vulkan, corresponds to @ref Vk::MeshIndexType and is convertible to
convertible to it using @ref Vk::vkIndexType(). See documentation of each value it using @ref Vk::meshIndexType(). See documentation of each value for more
for more information about the mapping. Note that not every type is available information about the mapping.
there, use @ref Vk::hasVkIndexType() to check for its presence.
@see @ref meshIndexTypeSize() @see @ref meshIndexTypeSize()
*/ */
enum class MeshIndexType: UnsignedByte { enum class MeshIndexType: UnsignedByte {
/* Zero reserved for an invalid type (but not being a named value) */ /* Zero reserved for an invalid type (but not being a named value) */
/** /**
* Unsigned byte * @relativeref{Magnum,UnsignedByte}.
* *
* Corresponds to @ref GL::MeshIndexType::UnsignedByte / * Corresponds to @ref GL::MeshIndexType::UnsignedByte /
* @val_vk_keyword{INDEX_TYPE_UINT8_EXT,IndexType}. Note that using this * @ref Vk::MeshIndexType::UnsignedByte. Even though OpenGL supports this
* type is discouraged, at least AMD GPUs are known to suggest (via debug * type and Vulkan can as well via an extension, using this type is
* output) using 16-byte types instead for better efficiency. * discouraged on contemporary GPU architectures. Prefer using 16-bit
* indices instead.
*/ */
UnsignedByte = 1, UnsignedByte = 1,
/** /**
* Unsigned short * @relativeref{Magnum,UnsignedShort}.
* *
* Corresponds to @ref GL::MeshIndexType::UnsignedShort / * Corresponds to @ref GL::MeshIndexType::UnsignedShort /
* @val_vk_keyword{INDEX_TYPE_UINT16,IndexType}. * @ref Vk::MeshIndexType::UnsignedShort.
*/ */
UnsignedShort, UnsignedShort,
/** /**
* Unsigned int * @relativeref{Magnum,UnsignedInt}.
* *
* Corresponds to @ref GL::MeshIndexType::UnsignedInt / * Corresponds to @ref GL::MeshIndexType::UnsignedInt /
* @val_vk_keyword{INDEX_TYPE_UINT32,IndexType}. * @ref Vk::MeshIndexType::UnsignedInt.
*/ */
UnsignedInt UnsignedInt
}; };

13
src/Magnum/MeshTools/sceneconverter.cpp

@ -86,7 +86,7 @@ magnum-sceneconverter [-h|--help] [-I|--importer IMPORTER]
Arguments: Arguments:
- `input` --- input file - `input` --- input file
- `output` --- output file, ignored if `--info` is present - `output` --- output file; ignored if `--info` is present
- `-h`, `--help` --- display this help message and exit - `-h`, `--help` --- display this help message and exit
- `-I`, `--importer IMPORTER` --- scene importer plugin (default: - `-I`, `--importer IMPORTER` --- scene importer plugin (default:
@ref Trade::AnySceneImporter "AnySceneImporter") @ref Trade::AnySceneImporter "AnySceneImporter")
@ -199,7 +199,7 @@ UnsignedInt namedAttributeId(const Trade::MeshData& mesh, UnsignedInt id) {
int main(int argc, char** argv) { int main(int argc, char** argv) {
Utility::Arguments args; Utility::Arguments args;
args.addArgument("input").setHelp("input", "input file") args.addArgument("input").setHelp("input", "input file")
.addArgument("output").setHelp("output", "output file, ignored if --info is present") .addArgument("output").setHelp("output", "output file; ignored if --info is present")
.addOption('I', "importer", "AnySceneImporter").setHelp("importer", "scene importer plugin") .addOption('I', "importer", "AnySceneImporter").setHelp("importer", "scene importer plugin")
.addArrayOption('C', "converter").setHelp("converter", "scene converter plugin(s)") .addArrayOption('C', "converter").setHelp("converter", "scene converter plugin(s)")
.addOption("plugin-dir").setHelp("plugin-dir", "override base plugin dir", "DIR") .addOption("plugin-dir").setHelp("plugin-dir", "override base plugin dir", "DIR")
@ -241,6 +241,15 @@ save its output; if no -C / --converter is specified, AnySceneConverter is
used.)") used.)")
.parse(argc, argv); .parse(argc, argv);
/* Generic checks */
if(!args.value<Containers::StringView>("output").isEmpty()) {
/* Not an error in this case, it should be possible to just append
--info to existing command line without having to remove anything.
But print a warning at least, it could also be a mistyped option. */
if(args.isSet("info"))
Warning{} << "Ignoring output file for --info:" << args.value<Containers::StringView>("output");
}
PluginManager::Manager<Trade::AbstractImporter> importerManager{ PluginManager::Manager<Trade::AbstractImporter> importerManager{
args.value("plugin-dir").empty() ? std::string{} : args.value("plugin-dir").empty() ? std::string{} :
Utility::Directory::join(args.value("plugin-dir"), Trade::AbstractImporter::pluginSearchPaths()[0])}; Utility::Directory::join(args.value("plugin-dir"), Trade::AbstractImporter::pluginSearchPaths()[0])};

28
src/Magnum/ShaderTools/AbstractConverter.cpp

@ -741,32 +741,4 @@ Debug& operator<<(Debug& debug, const Format value) {
return debug << "(" << Debug::nospace << reinterpret_cast<void*>(UnsignedByte(value)) << Debug::nospace << ")"; return debug << "(" << Debug::nospace << reinterpret_cast<void*>(UnsignedByte(value)) << Debug::nospace << ")";
} }
Debug& operator<<(Debug& debug, const Stage value) {
debug << "ShaderTools::Stage" << Debug::nospace;
switch(value) {
/* LCOV_EXCL_START */
#define _c(v) case Stage::v: return debug << "::" #v;
_c(Unspecified)
_c(Vertex)
_c(Fragment)
_c(Geometry)
_c(TessellationControl)
_c(TessellationEvaluation)
_c(Compute)
_c(RayGeneration)
_c(RayAnyHit)
_c(RayClosestHit)
_c(RayMiss)
_c(RayIntersection)
_c(RayCallable)
_c(MeshTask)
_c(Mesh)
#undef _c
/* LCOV_EXCL_STOP */
}
return debug << "(" << Debug::nospace << reinterpret_cast<void*>(UnsignedByte(value)) << Debug::nospace << ")";
}
}} }}

56
src/Magnum/ShaderTools/AbstractConverter.h

@ -26,7 +26,7 @@
*/ */
/** @file /** @file
* @brief Class @ref Magnum::ShaderTools::AbstractConverter, enum @ref Magnum::ShaderTools::ConverterFeature, @ref Magnum::ShaderTools::ConverterFlag, @ref Magnum::ShaderTools::Format, @ref Magnum::ShaderTools::Stage, enum set @ref Magnum::ShaderTools::ConverterFeatures, @ref Magnum::ShaderTools::ConverterFlags * @brief Class @ref Magnum::ShaderTools::AbstractConverter, enum @ref Magnum::ShaderTools::ConverterFeature, @ref Magnum::ShaderTools::ConverterFlag, @ref Magnum::ShaderTools::Format, enum set @ref Magnum::ShaderTools::ConverterFeatures, @ref Magnum::ShaderTools::ConverterFlags
* @m_since_latest * @m_since_latest
*/ */
@ -34,8 +34,13 @@
#include <Corrade/PluginManager/AbstractManagingPlugin.h> #include <Corrade/PluginManager/AbstractManagingPlugin.h>
#include "Magnum/Magnum.h" #include "Magnum/Magnum.h"
#include "Magnum/ShaderTools/ShaderTools.h"
#include "Magnum/ShaderTools/visibility.h" #include "Magnum/ShaderTools/visibility.h"
#ifdef MAGNUM_BUILD_DEPRECATED
#include "Magnum/ShaderTools/Stage.h"
#endif
namespace Magnum { namespace ShaderTools { namespace Magnum { namespace ShaderTools {
/** /**
@ -264,55 +269,6 @@ enum class Format: UnsignedInt {
*/ */
MAGNUM_SHADERTOOLS_EXPORT Debug& operator<<(Debug& debug, Format value); MAGNUM_SHADERTOOLS_EXPORT Debug& operator<<(Debug& debug, Format value);
/**
@brief Shader stage
@m_since_latest
@see @ref AbstractConverter
*/
enum class Stage: UnsignedInt {
/**
* Unspecified stage. When used in the
* @ref AbstractConverter::validateFile(),
* @ref AbstractConverter::convertFileToFile() "convertFileToFile()",
* @ref AbstractConverter::convertFileToData() "convertFileToData()",
* @ref AbstractConverter::linkFilesToFile() "linkFilesToFile()" or
* @ref AbstractConverter::linkFilesToData() "linkFilesToData()" APIs,
* particular plugins may attempt to detect the stage from filename, the
* shader stage might also be encoded directly in certain
* @ref Format "Format"s. Leaving the stage unspecified might limit
* validation and conversion capabilities, see documentation of a
* particular converter for concrete behavior.
*
* This value is guaranteed to be @cpp 0 @ce, which means you're encouraged
* to simply use @cpp {} @ce in function calls and elsewhere.
*/
Unspecified = 0,
Vertex, /**< Vertex stage */
Fragment, /**< Fragment stage */
Geometry, /**< Geometry stage */
TessellationControl, /**< Tessellation control stage */
TessellationEvaluation, /**< Tessellation evaluation stage */
Compute, /**< Compute stage */
RayGeneration, /**< Ray generation stage */
RayAnyHit, /**< Ray any hit stage */
RayClosestHit, /**< Ray closest hit stage */
RayMiss, /**< Ray miss stage */
RayIntersection, /**< Ray intersection stage */
RayCallable, /**< Ray callable stage */
MeshTask, /**< Mesh task stage */
Mesh /**< Mesh stage */
};
/**
@debugoperatorenum{Stage}
@m_since_latest
*/
MAGNUM_SHADERTOOLS_EXPORT Debug& operator<<(Debug& debug, Stage value);
/** /**
@brief Base for shader converter plugins @brief Base for shader converter plugins
@m_since_latest @m_since_latest

14
src/Magnum/ShaderTools/CMakeLists.txt

@ -30,14 +30,19 @@ set(MagnumShaderTools_SRCS )
# Files compiled with different flags for main library and unit test library # Files compiled with different flags for main library and unit test library
set(MagnumShaderTools_GracefulAssert_SRCS set(MagnumShaderTools_GracefulAssert_SRCS
AbstractConverter.cpp) AbstractConverter.cpp
Stage.cpp)
set(MagnumShaderTools_HEADERS set(MagnumShaderTools_HEADERS
AbstractConverter.h AbstractConverter.h
ShaderTools.h ShaderTools.h
Stage.h
visibility.h) visibility.h)
set(MagnumShaderTools_PRIVATE_HEADERS
Implementation/spirv.h)
if(NOT CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT) if(NOT CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake
${CMAKE_CURRENT_BINARY_DIR}/configure.h) ${CMAKE_CURRENT_BINARY_DIR}/configure.h)
@ -46,7 +51,8 @@ endif()
# # Objects shared between main and test library # # Objects shared between main and test library
# add_library(MagnumShaderToolsObjects OBJECT # add_library(MagnumShaderToolsObjects OBJECT
# ${MagnumShaderTools_SRCS} # ${MagnumShaderTools_SRCS}
# ${MagnumShaderTools_HEADERS}) # # ${MagnumShaderTools_HEADERS}
# # ${MagnumShaderTools_PRIVATE_HEADERS})
# target_include_directories(MagnumShaderToolsObjects PUBLIC $<TARGET_PROPERTY:Magnum,INTERFACE_INCLUDE_DIRECTORIES>) # target_include_directories(MagnumShaderToolsObjects PUBLIC $<TARGET_PROPERTY:Magnum,INTERFACE_INCLUDE_DIRECTORIES>)
# if(NOT BUILD_STATIC) # if(NOT BUILD_STATIC)
# target_compile_definitions(MagnumShaderToolsObjects PRIVATE "MagnumShaderToolsObjects_EXPORTS") # target_compile_definitions(MagnumShaderToolsObjects PRIVATE "MagnumShaderToolsObjects_EXPORTS")
@ -59,7 +65,9 @@ endif()
# Main ShaderTools library # Main ShaderTools library
add_library(MagnumShaderTools ${SHARED_OR_STATIC} add_library(MagnumShaderTools ${SHARED_OR_STATIC}
# $<TARGET_OBJECTS:MagnumShaderToolsObjects> # $<TARGET_OBJECTS:MagnumShaderToolsObjects>
${MagnumShaderTools_GracefulAssert_SRCS}) ${MagnumShaderTools_GracefulAssert_SRCS}
${MagnumShaderTools_HEADERS}
${MagnumShaderTools_PRIVATE_HEADERS})
set_target_properties(MagnumShaderTools PROPERTIES set_target_properties(MagnumShaderTools PROPERTIES
DEBUG_POSTFIX "-d" DEBUG_POSTFIX "-d"
FOLDER "Magnum/ShaderTools") FOLDER "Magnum/ShaderTools")

183
src/Magnum/ShaderTools/Implementation/spirv.h

@ -0,0 +1,183 @@
#ifndef Magnum_ShaderTools_Implementation_spirv_h
#define Magnum_ShaderTools_Implementation_spirv_h
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021 Vladimír Vondruš <mosra@centrum.cz>
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.
*/
#include <Corrade/Containers/ArrayView.h>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Containers/Reference.h>
#include <Corrade/Containers/StringView.h>
#include "Magnum/Magnum.h"
#include "MagnumExternal/Vulkan/spirv.h"
namespace Magnum { namespace ShaderTools { namespace Implementation { namespace {
/* This is used by both magnum-shaderconverter and the Vk library for
SwiftShader workarounds but we don't want the Vk library to depend on
ShaderTools, so the minimal needed subset is made header-only.
Eventually this should be turned into a public API, but so far it's just a
bag of random functions with very specific usage patterns and isn't clear
yet how to expose a usable interface. Moreover, the SwiftShader patching
needs to mutate the original, which means the outputs are pointers to the
original data. */
/* If the code looks like a valid SPIR-V, returns everything after the
header. If not, nullptr. */
Containers::ArrayView<const UnsignedInt> spirvData(const void* code, UnsignedInt size) {
const UnsignedInt* const spirv = static_cast<const UnsignedInt*>(code);
/* Not >= 5*4 because just the header alone is useless also */
return size % 4 == 0 && size > 5*4 && spirv[0] == SpvMagicNumber ?
Containers::ArrayView<const UnsignedInt>{spirv, size/4}.suffix(5) : nullptr;
}
/* When an instruction is found, the `data` is advanced after it in order to
allow calling this function in a loop. When not found, `data` is left
untouched. */
Containers::ArrayView<const UnsignedInt> spirvFindInstruction(Containers::ArrayView<const UnsignedInt>& data, const SpvOp op) {
/* Copy the view and iterate that. If we find the instruction, update the
passed `data` reference, if not, keep it as it was -- that way, if the
find fails, `data` won't become empty and can be used further */
for(Containers::ArrayView<const UnsignedInt> dataIteration = data; !dataIteration.empty(); ) {
const UnsignedInt instructionSize = dataIteration[0] >> 16;
const UnsignedInt instructionOp = dataIteration[0] & 0xffff;
/* Corrupted SPIR-V */
/** @todo print a message here? */
if(dataIteration.size() < instructionSize) {
data = dataIteration;
return nullptr;
}
/* This is the instruction we're looking for, return it and update the
view to point after it. */
if(instructionOp == op) {
data = dataIteration.suffix(instructionSize);
return dataIteration.prefix(instructionSize);
}
/* Otherwise advance the view for next round */
dataIteration = dataIteration.suffix(instructionSize);
}
/* Nothing found. Leave the input data as-is. */
return nullptr;
}
struct SpirvEntrypoint {
Containers::Reference<const SpvExecutionModel> executionModel;
Containers::StringView name;
Containers::ArrayView<const UnsignedInt> interfaces;
};
/* When an entrypoint is found, the `data` is advanced after the instruction in
order to allow calling this function in a loop. When not found, `data` is
left untouched. Most of other SPIR-V code is meant to appear after the
entrypoints, so it's fine to feed the resulting `data` to
spirvEntrypointInterface() and others. */
Containers::Optional<SpirvEntrypoint> spirvNextEntrypoint(Containers::ArrayView<const UnsignedInt>& data) {
while(const Containers::ArrayView<const UnsignedInt> entryPoint = spirvFindInstruction(data, SpvOpEntryPoint)) {
/* Expecting at least op, execution model, ID, name. If less, it's an
invalid SPIR-V. */
/** @todo print a message here? */
if(entryPoint.size() < 4) return {};
/* Find where the name ends and interface IDs start. According to the
spec, a string literal is null-terminated and all bytes after are
zeros as well, so it should be enough to check that the last byte is
zero. */
Containers::ArrayView<const UnsignedInt> interfaces;
for(std::size_t i = 3; i != entryPoint.size(); ++i) {
if(entryPoint[i] >> 24 == 0) {
interfaces = entryPoint.suffix(i + 1);
break;
}
}
return SpirvEntrypoint{
*reinterpret_cast<const SpvExecutionModel*>(entryPoint + 1),
reinterpret_cast<const char*>(entryPoint + 3),
interfaces
};
}
return {};
}
struct SpirvEntrypointInterface {
/* If null, the interface might be for example builtin */
const UnsignedInt* location;
/* If null, the SPIR-V is probably invalid */
const SpvStorageClass* storageClass;
};
/* Unlike above, `data` isn't modified by this function -- because the
decoration and variable instructions are likely intermixed for different
entrypoint, it makes sense to restart the search from the beginning for each
entrypoint.
The `out` array is expected to have the same size as entrypoint.interfaces
and be zero-initialized (so the not found data stay null). */
void spirvEntrypointInterface(Containers::ArrayView<const UnsignedInt> data, const SpirvEntrypoint& entrypoint, Containers::ArrayView<SpirvEntrypointInterface> out) {
CORRADE_INTERNAL_ASSERT(out.size() == entrypoint.interfaces.size());
/* Find location decorations */
while(const Containers::ArrayView<const UnsignedInt> decoration = spirvFindInstruction(data, SpvOpDecorate)) {
/* Expecting at least op, ID, SpvDecorationLocation, location. The
instruction can be three words, so if we get less than 4 it's not an
error. */
if(decoration.size() < 4 || decoration[2] != SpvDecorationLocation)
continue;
for(std::size_t i = 0; i != entrypoint.interfaces.size(); ++i) {
if(decoration[1] == entrypoint.interfaces[i]) {
out[i].location = decoration + 3;
break;
}
}
}
/* Find storage classes. According to the spec, OpVariable is meant to
appear after OpDecorate, so we don't need to restart from the
beginning. */
while(const Containers::ArrayView<const UnsignedInt> variable = spirvFindInstruction(data, SpvOpVariable)) {
/* Expecting at least op, result, ID, SpvStorageClass. If less, it's an
invalid SPIR-V. */
/** @todo print a message here? */
if(variable.size() < 4) return;
for(std::size_t i = 0; i != entrypoint.interfaces.size(); ++i) {
if(variable[2] == entrypoint.interfaces[i]) {
out[i].storageClass = reinterpret_cast<const SpvStorageClass*>(variable + 3);
break;
}
}
}
}
}}}}
#endif

9
src/Magnum/ShaderTools/ShaderTools.h

@ -1,5 +1,5 @@
#ifndef Magnum_Trade_ShaderTools_h #ifndef Magnum_ShaderTools_ShaderTools_h
#define Magnum_Trade_ShaderTools_h #define Magnum_ShaderTools_ShaderTools_h
/* /*
This file is part of Magnum. This file is part of Magnum.
@ -29,11 +29,14 @@
* @brief Forward declarations for the @ref Magnum::ShaderTools namespace * @brief Forward declarations for the @ref Magnum::ShaderTools namespace
*/ */
namespace Magnum { namespace Trade { #include "Magnum/Types.h"
namespace Magnum { namespace ShaderTools {
#ifndef DOXYGEN_GENERATING_OUTPUT #ifndef DOXYGEN_GENERATING_OUTPUT
class AbstractConverter; class AbstractConverter;
#endif #endif
enum class Stage: UnsignedInt;
}} }}

61
src/Magnum/ShaderTools/Stage.cpp

@ -0,0 +1,61 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021 Vladimír Vondruš <mosra@centrum.cz>
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.
*/
#include "Stage.h"
#include <Corrade/Utility/Debug.h>
namespace Magnum { namespace ShaderTools {
Debug& operator<<(Debug& debug, const Stage value) {
debug << "ShaderTools::Stage" << Debug::nospace;
switch(value) {
/* LCOV_EXCL_START */
#define _c(v) case Stage::v: return debug << "::" #v;
_c(Unspecified)
_c(Vertex)
_c(Fragment)
_c(Geometry)
_c(TessellationControl)
_c(TessellationEvaluation)
_c(Compute)
_c(RayGeneration)
_c(RayAnyHit)
_c(RayClosestHit)
_c(RayMiss)
_c(RayIntersection)
_c(RayCallable)
_c(MeshTask)
_c(Mesh)
_c(Kernel)
#undef _c
/* LCOV_EXCL_STOP */
}
return debug << "(" << Debug::nospace << reinterpret_cast<void*>(UnsignedByte(value)) << Debug::nospace << ")";
}
}}

97
src/Magnum/ShaderTools/Stage.h

@ -0,0 +1,97 @@
#ifndef Magnum_ShaderTools_Stage_h
#define Magnum_ShaderTools_Stage_h
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021 Vladimír Vondruš <mosra@centrum.cz>
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.
*/
/** @file
* @brief Enum @ref Magnum::ShaderTools::Stage
* @m_since_latest
*/
#include "Magnum/Magnum.h"
#include "Magnum/ShaderTools/visibility.h"
namespace Magnum { namespace ShaderTools {
/**
@brief Shader stage
@m_since_latest
@see @ref AbstractConverter
*/
enum class Stage: UnsignedInt {
/**
* Unspecified stage. When used in the
* @ref AbstractConverter::validateFile(),
* @ref AbstractConverter::convertFileToFile() "convertFileToFile()",
* @ref AbstractConverter::convertFileToData() "convertFileToData()",
* @ref AbstractConverter::linkFilesToFile() "linkFilesToFile()" or
* @ref AbstractConverter::linkFilesToData() "linkFilesToData()" APIs,
* particular plugins may attempt to detect the stage from filename, the
* shader stage might also be encoded directly in certain
* @ref Format "Format"s. Leaving the stage unspecified might limit
* validation and conversion capabilities, see documentation of a
* particular converter for concrete behavior.
*
* This value is guaranteed to be @cpp 0 @ce, which means you're encouraged
* to simply use @cpp {} @ce in function calls and elsewhere.
*/
Unspecified = 0,
Vertex, /**< Vertex stage */
Fragment, /**< Fragment stage */
Geometry, /**< Geometry stage */
TessellationControl, /**< Tessellation control stage */
TessellationEvaluation, /**< Tessellation evaluation stage */
Compute, /**< Compute stage */
RayGeneration, /**< Ray generation stage */
RayAnyHit, /**< Ray any hit stage */
RayClosestHit, /**< Ray closest hit stage */
RayMiss, /**< Ray miss stage */
RayIntersection, /**< Ray intersection stage */
RayCallable, /**< Ray callable stage */
MeshTask, /**< Mesh task stage */
Mesh, /**< Mesh stage */
/**
* Compute kernel.
*
* Compared to @ref Stage::Compute, which is used by graphics APIs such as
* Vulkan or OpenGL, this one is for use by OpenCL.
*/
Kernel
};
/**
@debugoperatorenum{Stage}
@m_since_latest
*/
MAGNUM_SHADERTOOLS_EXPORT Debug& operator<<(Debug& debug, Stage value);
}}
#endif

12
src/Magnum/ShaderTools/Test/AbstractConverterTest.cpp

@ -36,6 +36,7 @@
#include "Magnum/FileCallback.h" #include "Magnum/FileCallback.h"
#include "Magnum/ShaderTools/AbstractConverter.h" #include "Magnum/ShaderTools/AbstractConverter.h"
#include "Magnum/ShaderTools/Stage.h"
#include "configure.h" #include "configure.h"
@ -180,7 +181,6 @@ struct AbstractConverterTest: TestSuite::Tester {
void debugFlag(); void debugFlag();
void debugFlags(); void debugFlags();
void debugFormat(); void debugFormat();
void debugStage();
}; };
AbstractConverterTest::AbstractConverterTest() { AbstractConverterTest::AbstractConverterTest() {
@ -319,8 +319,7 @@ AbstractConverterTest::AbstractConverterTest() {
&AbstractConverterTest::debugFeatures, &AbstractConverterTest::debugFeatures,
&AbstractConverterTest::debugFlag, &AbstractConverterTest::debugFlag,
&AbstractConverterTest::debugFlags, &AbstractConverterTest::debugFlags,
&AbstractConverterTest::debugFormat, &AbstractConverterTest::debugFormat});
&AbstractConverterTest::debugStage});
/* Create testing dir */ /* Create testing dir */
Utility::Directory::mkpath(SHADERTOOLS_TEST_OUTPUT_DIR); Utility::Directory::mkpath(SHADERTOOLS_TEST_OUTPUT_DIR);
@ -3428,13 +3427,6 @@ void AbstractConverterTest::debugFormat() {
CORRADE_COMPARE(out.str(), "ShaderTools::Format::Glsl ShaderTools::Format(0xf0)\n"); CORRADE_COMPARE(out.str(), "ShaderTools::Format::Glsl ShaderTools::Format(0xf0)\n");
} }
void AbstractConverterTest::debugStage() {
std::ostringstream out;
Debug{&out} << Stage::RayMiss << Stage(0xf0);
CORRADE_COMPARE(out.str(), "ShaderTools::Stage::RayMiss ShaderTools::Stage(0xf0)\n");
}
}}}} }}}}
CORRADE_TEST_MAIN(Magnum::ShaderTools::Test::AbstractConverterTest) CORRADE_TEST_MAIN(Magnum::ShaderTools::Test::AbstractConverterTest)

8
src/Magnum/ShaderTools/Test/CMakeLists.txt

@ -37,9 +37,15 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake
corrade_add_test(ShaderToolsAbstractConverterTest AbstractConverterTest.cpp corrade_add_test(ShaderToolsAbstractConverterTest AbstractConverterTest.cpp
LIBRARIES MagnumShaderToolsTestLib LIBRARIES MagnumShaderToolsTestLib
FILES file.dat another.dat) FILES file.dat another.dat)
target_include_directories(ShaderToolsAbstractConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) target_include_directories(ShaderToolsAbstractConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
corrade_add_test(ShaderToolsSpirvTest SpirvTest.cpp
LIBRARIES MagnumShaderTools
FILES SpirvTestFiles/entrypoint-interface.spv)
target_include_directories(ShaderToolsSpirvTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
corrade_add_test(ShaderToolsStageTest StageTest.cpp LIBRARIES MagnumShaderTools)
set_target_properties( set_target_properties(
ShaderToolsAbstractConverterTest ShaderToolsAbstractConverterTest
ShaderToolsSpirvTest
ShaderToolsStageTest
PROPERTIES FOLDER "Magnum/ShaderTools/Test") PROPERTIES FOLDER "Magnum/ShaderTools/Test")

265
src/Magnum/ShaderTools/Test/SpirvTest.cpp

@ -0,0 +1,265 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021 Vladimír Vondruš <mosra@centrum.cz>
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.
*/
#include <string>
#include <Corrade/Containers/Array.h>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/Utility/Directory.h>
#include "Magnum/ShaderTools/Implementation/spirv.h"
#include "MagnumExternal/Vulkan/spirv.h"
#include "configure.h"
namespace Magnum { namespace ShaderTools { namespace Test { namespace {
struct SpirvTest: TestSuite::Tester {
explicit SpirvTest();
void data();
void dataInvalid();
void findInstruction();
void findInstructionNotEnoughData();
void nextEntrypoint();
void nextEntrypointInvalidInstruction();
void entrypointInterface();
void entrypointInterfaceNothing();
};
const UnsignedInt Data[] {
SpvMagicNumber, SpvVersion, 0, 66, 0,
0 /* first instruction */
};
const UnsignedInt JustHeader[]{
SpvMagicNumber, SpvVersion, 0, 66, 0
};
const UnsignedInt InvalidMagic[]{
SpvMagicNumber + 1, SpvVersion, 0, 66, 0,
0 /* first instruction */
};
const struct {
const char* name;
Containers::ArrayView<const void> data;
} InvalidData[] {
{"empty", {}},
/* GCC 4.8 needs the ArrayView conversion explicit */
{"just the header", Containers::arrayView(JustHeader)},
{"invalid magic", Containers::arrayView(InvalidMagic)},
{"size not divisible by four", Containers::arrayCast<const char>(Data).except(1)}
};
SpirvTest::SpirvTest() {
addTests({&SpirvTest::data});
addInstancedTests({&SpirvTest::dataInvalid},
Containers::arraySize(InvalidData));
addTests({&SpirvTest::findInstruction,
&SpirvTest::findInstructionNotEnoughData,
&SpirvTest::nextEntrypoint,
&SpirvTest::nextEntrypointInvalidInstruction,
&SpirvTest::entrypointInterface,
&SpirvTest::entrypointInterfaceNothing});
}
void SpirvTest::data() {
CORRADE_COMPARE(Implementation::spirvData(Data, sizeof(Data)), Containers::arrayView(Data + 5, 1));
}
void SpirvTest::dataInvalid() {
auto&& data = InvalidData[testCaseInstanceId()];
setTestCaseDescription(data.name);
CORRADE_VERIFY(!Implementation::spirvData(data.data, data.data.size()));
}
UnsignedInt op(UnsignedInt length, SpvOp op) {
return length << 16 | op;
}
void SpirvTest::findInstruction() {
const UnsignedInt data[] {
op(3, SpvOpMemoryModel), SpvAddressingModelLogical, SpvMemoryModelGLSL450,
op(4, SpvOpDecorate), 12, SpvDecorationLocation, 0,
op(1, SpvOpNop),
op(1, SpvOpNop),
op(4, SpvOpDecorate), 13, SpvDecorationLocation, 1,
};
Containers::ArrayView<const UnsignedInt> view = data;
Containers::ArrayView<const UnsignedInt> decorate1 = Implementation::spirvFindInstruction(view, SpvOpDecorate);
CORRADE_COMPARE(decorate1.size(), 4);
CORRADE_COMPARE(decorate1.data(), data + 3);
CORRADE_COMPARE(view.data(), data + 7);
/* Verify a single-word instruction works too */
Containers::ArrayView<const UnsignedInt> nop = Implementation::spirvFindInstruction(view, SpvOpNop);
CORRADE_COMPARE(nop.size(), 1);
CORRADE_COMPARE(nop.data(), data + 7);
CORRADE_COMPARE(view.data(), data + 8);
Containers::ArrayView<const UnsignedInt> decorate2 = Implementation::spirvFindInstruction(view, SpvOpDecorate);
CORRADE_COMPARE(decorate2.size(), 4);
CORRADE_COMPARE(decorate2.data(), data + 9);
CORRADE_COMPARE(view.data(), data + 13);
/* We're at the end, there's no more OpDecorate instructions to find */
CORRADE_VERIFY(!Implementation::spirvFindInstruction(view, SpvOpDecorate));
}
void SpirvTest::findInstructionNotEnoughData() {
const UnsignedInt data[] {
op(3, SpvOpMemoryModel), SpvAddressingModelLogical, SpvMemoryModelGLSL450,
/* Should be 4 */
op(5, SpvOpDecorate), 12, SpvDecorationLocation, 0
};
Containers::ArrayView<const UnsignedInt> view = data;
CORRADE_VERIFY(!Implementation::spirvFindInstruction(view, SpvOpDecorate));
/* View gets set to the first invalid instruction */
CORRADE_COMPARE(view.data(), data + 3);
}
void SpirvTest::nextEntrypoint() {
Containers::Array<char> data = Utility::Directory::read(Utility::Directory::join(SHADERTOOLS_TEST_DIR, "SpirvTestFiles/entrypoint-interface.spv"));
/* The file is a full SPIR-V, strip the header first */
Containers::ArrayView<const UnsignedInt> view = Implementation::spirvData(data, data.size());
CORRADE_VERIFY(view);
Containers::Optional<Implementation::SpirvEntrypoint> vert = Implementation::spirvNextEntrypoint(view);
CORRADE_VERIFY(vert);
/* Verify that long names get recognized properly */
CORRADE_COMPARE(vert->name, "vertexLongEntrypointName");
CORRADE_COMPARE(vert->executionModel, SpvExecutionModelVertex);
/* We don't care about the contents, those would change with each assembly
anyway. Verified fully in entrypointInterface(). */
CORRADE_COMPARE(vert->interfaces.size(), 4);
Containers::Optional<Implementation::SpirvEntrypoint> frag = Implementation::spirvNextEntrypoint(view);
CORRADE_VERIFY(frag);
CORRADE_COMPARE(frag->name, "fra");
CORRADE_COMPARE(frag->executionModel, SpvExecutionModelFragment);
CORRADE_COMPARE(frag->interfaces.size(), 3);
/* Only two entrypoints in this file */
CORRADE_VERIFY(!Implementation::spirvNextEntrypoint(view));
}
void SpirvTest::nextEntrypointInvalidInstruction() {
const UnsignedInt data[] {
op(3, SpvOpMemoryModel), SpvAddressingModelLogical, SpvMemoryModelGLSL450,
/* Should be 4 (missing name) */
op(3, SpvOpEntryPoint), SpvExecutionModelVertex, 1
};
Containers::ArrayView<const UnsignedInt> view = data;
CORRADE_VERIFY(!Implementation::spirvNextEntrypoint(view));
}
void SpirvTest::entrypointInterface() {
Containers::Array<char> data = Utility::Directory::read(Utility::Directory::join(SHADERTOOLS_TEST_DIR, "SpirvTestFiles/entrypoint-interface.spv"));
/* The file is a full SPIR-V, strip the header first */
Containers::ArrayView<const UnsignedInt> view = Implementation::spirvData(data, data.size());
CORRADE_VERIFY(view);
Containers::Optional<Implementation::SpirvEntrypoint> vert = Implementation::spirvNextEntrypoint(view);
CORRADE_VERIFY(vert);
CORRADE_COMPARE(vert->interfaces.size(), 4);
Implementation::SpirvEntrypointInterface vertInterface[4]{};
Implementation::spirvEntrypointInterface(view, *vert, vertInterface);
CORRADE_VERIFY(vertInterface[0].location); /* position */
CORRADE_VERIFY(vertInterface[0].storageClass);
CORRADE_COMPARE(*vertInterface[0].location, 0);
CORRADE_COMPARE(*vertInterface[0].storageClass, SpvStorageClassInput);
CORRADE_VERIFY(vertInterface[1].location); /* color */
CORRADE_VERIFY(vertInterface[1].storageClass);
CORRADE_COMPARE(*vertInterface[1].location, 1);
CORRADE_COMPARE(*vertInterface[1].storageClass, SpvStorageClassInput);
/* Verify that absence of location is handled properly */
CORRADE_VERIFY(!vertInterface[2].location); /* gl_Position */
CORRADE_VERIFY(vertInterface[2].storageClass);
CORRADE_COMPARE(*vertInterface[2].storageClass, SpvStorageClassOutput);
CORRADE_VERIFY(vertInterface[3].location); /* interpolatedColorOut */
CORRADE_VERIFY(vertInterface[2].storageClass);
CORRADE_COMPARE(*vertInterface[3].location, 0);
CORRADE_COMPARE(*vertInterface[2].storageClass, SpvStorageClassOutput);
Containers::Optional<Implementation::SpirvEntrypoint> frag = Implementation::spirvNextEntrypoint(view);
CORRADE_VERIFY(frag);
CORRADE_COMPARE(frag->interfaces.size(), 3);
Implementation::SpirvEntrypointInterface fragInterface[3]{};
Implementation::spirvEntrypointInterface(view, *frag, fragInterface);
CORRADE_VERIFY(fragInterface[0].location); /* interpolatedColorIn */
CORRADE_VERIFY(fragInterface[0].storageClass);
CORRADE_COMPARE(*fragInterface[0].location, 0);
CORRADE_COMPARE(*fragInterface[0].storageClass, SpvStorageClassInput);
CORRADE_VERIFY(fragInterface[1].location); /* fragmentColor */
CORRADE_VERIFY(fragInterface[1].storageClass);
CORRADE_COMPARE(*fragInterface[1].location, 0);
CORRADE_COMPARE(*fragInterface[1].storageClass, SpvStorageClassOutput);
/* Verify that absence of storageClass is handled properly */
CORRADE_VERIFY(fragInterface[2].location); /* unknownFragmentInterface */
CORRADE_VERIFY(!fragInterface[2].storageClass);
CORRADE_COMPARE(*fragInterface[2].location, 1);
}
void SpirvTest::entrypointInterfaceNothing() {
const UnsignedInt data[] {
op(3, SpvOpMemoryModel), SpvAddressingModelLogical, SpvMemoryModelGLSL450,
op(4, SpvOpEntryPoint), SpvExecutionModelGLCompute, 1, '\0'
};
Containers::ArrayView<const UnsignedInt> view = data;
Containers::Optional<Implementation::SpirvEntrypoint> comp = Implementation::spirvNextEntrypoint(view);
CORRADE_VERIFY(comp);
CORRADE_VERIFY(comp->interfaces.empty());
Implementation::spirvEntrypointInterface(view, *comp, {});
/* Well, it shouldn't crash */
CORRADE_VERIFY(true);
}
}}}}
CORRADE_TEST_MAIN(Magnum::ShaderTools::Test::SpirvTest)

5
src/Magnum/ShaderTools/Test/SpirvTestFiles/convert.sh

@ -0,0 +1,5 @@
#!/bin/bash
for i in $(ls *.spvasm); do
magnum-shaderconverter $i ${i%asm}
done

BIN
src/Magnum/ShaderTools/Test/SpirvTestFiles/entrypoint-interface.spv

Binary file not shown.

23
src/Magnum/ShaderTools/Test/SpirvTestFiles/entrypoint-interface.spvasm

@ -0,0 +1,23 @@
OpCapability Shader
OpEntryPoint Vertex %ver "vertexLongEntrypointName" %position %color %gl_Position %interpolatedColorOut
OpEntryPoint Fragment %fra "fra" %interpolatedColorIn %fragmentColor %unknownFragmentInterface
OpExecutionMode %fra OriginUpperLeft
OpDecorate %gl_Position BuiltIn Position
OpDecorate %position Location 0
OpDecorate %color Location 1
OpDecorate %fragmentColor Location 0
OpDecorate %unknownFragmentInterface Location 1
OpDecorate %interpolatedColorIn Location 0
OpDecorate %interpolatedColorOut Location 0
%void = OpTypeVoid
%10 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Input_v4float = OpTypePointer Input %v4float
%position = OpVariable %_ptr_Input_v4float Input
%color = OpVariable %_ptr_Input_v4float Input
%interpolatedColorIn = OpVariable %_ptr_Input_v4float Input
%_ptr_Output_v4float = OpTypePointer Output %v4float
%gl_Position = OpVariable %_ptr_Output_v4float Output
%interpolatedColorOut = OpVariable %_ptr_Output_v4float Output
%fragmentColor = OpVariable %_ptr_Output_v4float Output

53
src/Magnum/ShaderTools/Test/StageTest.cpp

@ -0,0 +1,53 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021 Vladimír Vondruš <mosra@centrum.cz>
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.
*/
#include <sstream>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/Utility/DebugStl.h>
#include "Magnum/ShaderTools/Stage.h"
namespace Magnum { namespace ShaderTools { namespace Test { namespace {
struct StageTest: TestSuite::Tester {
explicit StageTest();
void debug();
};
StageTest::StageTest() {
addTests({&StageTest::debug});
}
void StageTest::debug() {
std::ostringstream out;
Debug{&out} << Stage::RayMiss << Stage(0xf0);
CORRADE_COMPARE(out.str(), "ShaderTools::Stage::RayMiss ShaderTools::Stage(0xf0)\n");
}
}}}}
CORRADE_TEST_MAIN(Magnum::ShaderTools::Test::StageTest)

150
src/Magnum/ShaderTools/shaderconverter.cpp

@ -33,6 +33,8 @@
#include "Magnum/Implementation/converterUtilities.h" #include "Magnum/Implementation/converterUtilities.h"
#include "Magnum/Math/Functions.h" #include "Magnum/Math/Functions.h"
#include "Magnum/ShaderTools/AbstractConverter.h" #include "Magnum/ShaderTools/AbstractConverter.h"
#include "Magnum/ShaderTools/Stage.h"
#include "Magnum/ShaderTools/Implementation/spirv.h"
namespace Magnum { namespace Magnum {
@ -62,10 +64,10 @@ information.
@code{.sh} @code{.sh}
magnum-shaderconverter [-h|--help] [--validate] [--link] magnum-shaderconverter [-h|--help] [--validate] [--link]
[-C|--converter NAME]... [--plugin-dir DIR] [-C|--converter NAME]... [--plugin-dir DIR]
[-c|--converter-options key=val,key2=val2,]... [-q|--quiet] [-v|--verbose] [-c|--converter-options key=val,key2=val2,]... [--info] [-q|--quiet]
[--warning-as-error] [-E|--preprocess-only] [-D|--define name=value]... [-v|--verbose] [--warning-as-error] [-E|--preprocess-only]
[-U|--undefine name]... [-O|--optimize LEVEL] [-g|--debug-info LEVEL] [-D|--define name=value]... [-U|--undefine name]... [-O|--optimize LEVEL]
[--input-format glsl|spv|spvasm|hlsl|metal]... [-g|--debug-info LEVEL] [--input-format glsl|spv|spvasm|hlsl|metal]...
[--output-format glsl|spv|spvasm|hlsl|metal]... [--output-format glsl|spv|spvasm|hlsl|metal]...
[--input-version VERSION]... [--output-version VERSION]... [--input-version VERSION]... [--output-version VERSION]...
[--] input... output [--] input... output
@ -74,9 +76,10 @@ magnum-shaderconverter [-h|--help] [--validate] [--link]
Arguments: Arguments:
- `input` --- input file(s) - `input` --- input file(s)
- `output` --- output file, ignored if `--validate` is present. If neither - `output` --- output file; ignored if `--info` is present, disallowed for
`--validate` nor `--link` is present, corresponds to the `--validate`. If neither `--info`, `--validate` nor `--link` is present,
@ref ShaderTools::AbstractConverter::convertFileToFile() function. corresponds to the @ref ShaderTools::AbstractConverter::convertFileToFile()
function.
- `-h`, `--help` --- display this help message and exit - `-h`, `--help` --- display this help message and exit
- `--validate` --- validate input. Corresponds to the - `--validate` --- validate input. Corresponds to the
@ref ShaderTools::AbstractConverter::validateFile() function. @ref ShaderTools::AbstractConverter::validateFile() function.
@ -86,6 +89,7 @@ Arguments:
- `--plugin-dir DIR` --- override base plugin dir - `--plugin-dir DIR` --- override base plugin dir
- `-c`, `--converter-options key=val,key2=val2,` --- configuration options - `-c`, `--converter-options key=val,key2=val2,` --- configuration options
to pass to the converter(s) to pass to the converter(s)
- `--info` --- print SPIR-V module info and exit
- `-q`, `--quiet` --- quiet output from converter plugin(s). Corresponds to - `-q`, `--quiet` --- quiet output from converter plugin(s). Corresponds to
the @ref ShaderTools::ConverterFlag::Quiet flag. the @ref ShaderTools::ConverterFlag::Quiet flag.
- `-v`, `--verbose` --- verbose output from converter plugin(s). Corresponds - `-v`, `--verbose` --- verbose output from converter plugin(s). Corresponds
@ -169,15 +173,71 @@ magnum-shaderconverter phong.frag -DDIFFUSE_TEXTURE -DNORMAL_TEXTURE --input-ver
using namespace Corrade::Containers::Literals; using namespace Corrade::Containers::Literals;
using namespace Magnum; using namespace Magnum;
namespace {
ShaderTools::Stage spvExecutionModelToStage(const SpvExecutionModel model) {
switch(model) {
#define _c(model, stage) case SpvExecutionModel ## model: return ShaderTools::Stage::stage;
_c(Vertex, Vertex)
_c(Fragment, Fragment)
_c(Geometry, Geometry)
_c(TessellationControl, TessellationControl)
_c(TessellationEvaluation, TessellationEvaluation)
_c(GLCompute, Compute)
_c(RayGenerationKHR, RayGeneration)
_c(AnyHitKHR, RayAnyHit)
_c(ClosestHitKHR, RayClosestHit)
_c(MissKHR, RayMiss)
_c(IntersectionKHR, RayIntersection)
_c(CallableKHR, RayCallable)
_c(TaskNV, MeshTask)
_c(MeshNV, Mesh)
_c(Kernel, Kernel)
#undef _c
case SpvExecutionModelMax: break;
}
/* Encode unknown stages with the highest bit set. SpvExecutionModelMax is
0x7fffffff, so this shouldn't lead to any data loss. */
return ShaderTools::Stage((1u << 31)|model);
}
void printSpirvInfo(Containers::ArrayView<const UnsignedInt> data) {
while(Containers::Optional<ShaderTools::Implementation::SpirvEntrypoint> entrypoint = ShaderTools::Implementation::spirvNextEntrypoint(data)) {
Debug d;
d << "Entrypoint" << entrypoint->name << "(" << Debug::nospace << spvExecutionModelToStage(entrypoint->executionModel) << Debug::nospace << ")" << Debug::newline;
Containers::Array<ShaderTools::Implementation::SpirvEntrypointInterface> interface{Containers::ValueInit, entrypoint->interfaces.size()};
ShaderTools::Implementation::spirvEntrypointInterface(data, *entrypoint, interface);
for(const ShaderTools::Implementation::SpirvEntrypointInterface& i: interface) {
d << " ";
if(!i.storageClass) d << "(unknown)";
else if(*i.storageClass == SpvStorageClassInput)
d << "in";
else if(*i.storageClass == SpvStorageClassOutput)
d << "out";
else d << "SpvStorageClass(" << Debug::nospace << *i.storageClass << Debug::nospace << ")";
if(i.location) d << "(location=" << Debug::nospace << *i.location << Debug::nospace << ")";
d << Debug::newline;
}
}
}
}
int main(int argc, char** argv) { int main(int argc, char** argv) {
Utility::Arguments args; Utility::Arguments args;
args.addArrayArgument("input").setHelp("input", "input file(s)") args.addArrayArgument("input").setHelp("input", "input file(s)")
.addArgument("output").setHelp("output", "output file, ignored if --validate is present") .addArgument("output").setHelp("output", "output file; ignored if --info is present, disallowed for --validate")
.addBooleanOption("validate").setHelp("validate", "validate input") .addBooleanOption("validate").setHelp("validate", "validate input")
.addBooleanOption("link").setHelp("link", "link multiple input files together") .addBooleanOption("link").setHelp("link", "link multiple input files together")
.addArrayOption('C', "converter").setHelp("converter", "shader converter plugin(s)") .addArrayOption('C', "converter").setHelp("converter", "shader converter plugin(s)")
.addOption("plugin-dir").setHelp("plugin-dir", "override base plugin dir", "DIR") .addOption("plugin-dir").setHelp("plugin-dir", "override base plugin dir", "DIR")
.addArrayOption('c', "converter-options").setHelp("converter-options", "configuration options to pass to the converter(s)", "key=val,key2=val2,…") .addArrayOption('c', "converter-options").setHelp("converter-options", "configuration options to pass to the converter(s)", "key=val,key2=val2,…")
.addBooleanOption("info").setHelp("info", "print SPIR-V module info and exit")
.addBooleanOption('q', "quiet").setHelp("quiet", "quiet output from converter plugin(s)") .addBooleanOption('q', "quiet").setHelp("quiet", "quiet output from converter plugin(s)")
.addBooleanOption('v', "verbose").setHelp("verbose", "verbose output from converter plugin(s)") .addBooleanOption('v', "verbose").setHelp("verbose", "verbose output from converter plugin(s)")
.addBooleanOption("warning-as-error").setHelp("warning-as-error", "treat warnings as errors") .addBooleanOption("warning-as-error").setHelp("warning-as-error", "treat warnings as errors")
@ -192,15 +252,21 @@ int main(int argc, char** argv) {
.addArrayOption("input-version").setHelp("input-version", "input format version for each converter", "VERSION") .addArrayOption("input-version").setHelp("input-version", "input format version for each converter", "VERSION")
.addArrayOption("output-version").setHelp("output-version", "output format version for each converter", "VERSION") .addArrayOption("output-version").setHelp("output-version", "output format version for each converter", "VERSION")
.setParseErrorCallback([](const Utility::Arguments& args, Utility::Arguments::ParseError error, const std::string& key) { .setParseErrorCallback([](const Utility::Arguments& args, Utility::Arguments::ParseError error, const std::string& key) {
/* If --validate is passed, we don't need the output argument */ /* If --info / --validate is passed, we don't need the output
argument */
if(error == Utility::Arguments::ParseError::MissingArgument && if(error == Utility::Arguments::ParseError::MissingArgument &&
key == "output" && args.isSet("validate")) return true; key == "output" && (args.isSet("info") || args.isSet("validate")))
return true;
/* Handle all other errors as usual */ /* Handle all other errors as usual */
return false; return false;
}) })
.setGlobalHelp(R"(Converts, compiles, optimizes and links shaders of different formats. .setGlobalHelp(R"(Converts, compiles, optimizes and links shaders of different formats.
If --info is given and the input looks like a SPIR-V binary, the utility prints
information about its entrypoints using builtin SPIR-V reflection capabilities.
If it's not a SPIR-V binary, it's converted to it first.
If --validate is given, the utility will validate the input file using passed If --validate is given, the utility will validate the input file using passed
--converter (or AnyShaderConverter if none is specified), print the validation --converter (or AnyShaderConverter if none is specified), print the validation
log on output and exit with a non-zero code if the validation fails. If --link log on output and exit with a non-zero code if the validation fails. If --link
@ -231,11 +297,17 @@ see documentation of a particular converter for more information.)")
.parse(argc, argv); .parse(argc, argv);
/* Generic checks */ /* Generic checks */
if(args.isSet("validate")) {
if(!args.value<Containers::StringView>("output").isEmpty()) { if(!args.value<Containers::StringView>("output").isEmpty()) {
Error{} << "Output file shouldn't be set for --validate"; if(args.isSet("validate")) {
Error{} << "Output file shouldn't be set for --validate:" << args.value<Containers::StringView>("output");
return 1; return 1;
} }
/* Not an error in this case, it should be possible to just append
--info to existing command line without having to remove anything.
But print a warning at least, it could also be a mistyped option. */
if(args.isSet("info"))
Warning{} << "Ignoring output file for --info:" << args.value<Containers::StringView>("output");
} }
if(!args.isSet("link")) { if(!args.isSet("link")) {
if(args.arrayValueCount("input") != 1) { if(args.arrayValueCount("input") != 1) {
@ -263,6 +335,17 @@ see documentation of a particular converter for more information.)")
return 6; return 6;
} }
/* If we want just SPIR-V info and the input looks like a SPIR-V binary,
do that right away without going through any plugin. If it doesn't, we
try again after using a converter.s */
if(args.isSet("info")) {
const Containers::Array<char> data = Utility::Directory::read(args.arrayValue("input", 0));
if(Containers::ArrayView<const UnsignedInt> spirv = ShaderTools::Implementation::spirvData(data, data.size())) {
printSpirvInfo(spirv);
return 0;
}
}
/* Set up a converter manager */ /* Set up a converter manager */
PluginManager::Manager<ShaderTools::AbstractConverter> converterManager{ PluginManager::Manager<ShaderTools::AbstractConverter> converterManager{
args.value("plugin-dir").empty() ? std::string{} : args.value("plugin-dir").empty() ? std::string{} :
@ -285,8 +368,11 @@ see documentation of a particular converter for more information.)")
if(i < args.arrayValueCount("converter-options")) if(i < args.arrayValueCount("converter-options"))
Implementation::setOptions(*converter, args.arrayValue("converter-options", i)); Implementation::setOptions(*converter, args.arrayValue("converter-options", i));
/* Parse format, if passed */ /* Parse format, if passed. If --info is desired, implicitly set the
output format to SPIR-V */
ShaderTools::Format inputFormat{}, outputFormat{}; ShaderTools::Format inputFormat{}, outputFormat{};
if(args.isSet("info"))
outputFormat = ShaderTools::Format::Spirv;
auto parseFormat = [](Containers::StringView format) -> Containers::Optional<ShaderTools::Format> { auto parseFormat = [](Containers::StringView format) -> Containers::Optional<ShaderTools::Format> {
if(format == ""_s) return ShaderTools::Format::Unspecified; if(format == ""_s) return ShaderTools::Format::Unspecified;
if(format == "glsl"_s) return ShaderTools::Format::Glsl; if(format == "glsl"_s) return ShaderTools::Format::Glsl;
@ -334,7 +420,7 @@ see documentation of a particular converter for more information.)")
Containers::Array<std::pair<ShaderTools::Stage, Containers::StringView>> linkInputs; Containers::Array<std::pair<ShaderTools::Stage, Containers::StringView>> linkInputs;
if(i == 0) { if(i == 0) {
if((args.isSet("preprocess-only") || args.arrayValueCount("define") || args.arrayValueCount("undefine"))) { if((args.isSet("preprocess-only") || args.arrayValueCount("define") || args.arrayValueCount("undefine"))) {
if(!(converter->features() & ShaderTools::ConverterFeature::Preprocess)) { if(!(converter->features() >= ShaderTools::ConverterFeature::Preprocess)) {
Error{} << "The -E / -D / -U options are set, but" << converterName << "doesn't support preprocessing"; Error{} << "The -E / -D / -U options are set, but" << converterName << "doesn't support preprocessing";
return 10; return 10;
} }
@ -359,7 +445,7 @@ see documentation of a particular converter for more information.)")
} }
if(!args.value<Containers::StringView>("optimize").isEmpty()) { if(!args.value<Containers::StringView>("optimize").isEmpty()) {
if(!(converter->features() & ShaderTools::ConverterFeature::Optimize)) { if(!(converter->features() >= ShaderTools::ConverterFeature::Optimize)) {
Error{} << "The -O option is set, but" << converterName << "doesn't support optimization"; Error{} << "The -O option is set, but" << converterName << "doesn't support optimization";
return 11; return 11;
} }
@ -368,7 +454,7 @@ see documentation of a particular converter for more information.)")
} }
if(!args.value<Containers::StringView>("debug-info").isEmpty()) { if(!args.value<Containers::StringView>("debug-info").isEmpty()) {
if(!(converter->features() & ShaderTools::ConverterFeature::DebugInfo)) { if(!(converter->features() >= ShaderTools::ConverterFeature::DebugInfo)) {
Error{} << "The -g option is set, but" << converterName << "doesn't support debug info"; Error{} << "The -g option is set, but" << converterName << "doesn't support debug info";
return 12; return 12;
} }
@ -386,6 +472,32 @@ see documentation of a particular converter for more information.)")
converter->setFlags(flags); converter->setFlags(flags);
/* If we want just SPIR-V info, convert to a SPIR-V and exit */
if(args.isSet("info")) {
/* The info exits right after, so this branch shouldn't get
re-entered again */
CORRADE_INTERNAL_ASSERT(i == 0);
if(!(converter->features() >= ShaderTools::ConverterFeature::ConvertData)) {
Error{} << converterName << "doesn't support data conversion";
return 18; /* same code as the same message below */
}
if(!(data = converter->convertFileToData(ShaderTools::Stage::Unspecified, args.arrayValue<Containers::StringView>("input", 0)))) {
Error{} << "Cannot convert" << args.arrayValue<Containers::StringView>("input", 0);
return 20; /* same code as the same message below */
}
Containers::ArrayView<const UnsignedInt> spirv = ShaderTools::Implementation::spirvData(data, data.size());
if(!spirv) {
Error{} << "The output is not a SPIR-V binary, can't print info";
return 23;
}
printSpirvInfo(spirv);
return 0;
}
/* If validating, do it just with the first passed converter and then /* If validating, do it just with the first passed converter and then
exit */ exit */
if(args.isSet("validate")) { if(args.isSet("validate")) {
@ -393,7 +505,7 @@ see documentation of a particular converter for more information.)")
re-entered again */ re-entered again */
CORRADE_INTERNAL_ASSERT(i == 0); CORRADE_INTERNAL_ASSERT(i == 0);
if(!(converter->features() & ShaderTools::ConverterFeature::ValidateFile)) { if(!(converter->features() >= ShaderTools::ConverterFeature::ValidateFile)) {
Error{} << converterName << "doesn't support file validation"; Error{} << converterName << "doesn't support file validation";
return 13; return 13;
} }
@ -416,7 +528,7 @@ see documentation of a particular converter for more information.)")
/* This is the first *and* last --converter, go from a file to a file */ /* This is the first *and* last --converter, go from a file to a file */
if(i == 0 && converterCount <= 1) { if(i == 0 && converterCount <= 1) {
if(!(converter->features() & ShaderTools::ConverterFeature::ConvertFile)) { if(!(converter->features() >= ShaderTools::ConverterFeature::ConvertFile)) {
Error{} << converterName << "doesn't support file conversion"; Error{} << converterName << "doesn't support file conversion";
return 15; return 15;
} }
@ -440,7 +552,7 @@ see documentation of a particular converter for more information.)")
/* Otherwise we need to go through data */ /* Otherwise we need to go through data */
} else { } else {
if(!(converter->features() & ShaderTools::ConverterFeature::ConvertData)) { if(!(converter->features() >= ShaderTools::ConverterFeature::ConvertData)) {
Error{} << converterName << "doesn't support data conversion"; Error{} << converterName << "doesn't support data conversion";
return 18; return 18;
} }

2
src/Magnum/Trade/MeshData.h

@ -690,7 +690,7 @@ to map custom @ref MeshAttribute values to human-readable string names using
@ref AbstractImporter::meshAttributeName() and @ref AbstractImporter::meshAttributeName() and
@ref AbstractImporter::meshAttributeForName(). Using @ref meshPrimitiveWrap() @ref AbstractImporter::meshAttributeForName(). Using @ref meshPrimitiveWrap()
you can also supply implementation-specific values that are not available in you can also supply implementation-specific values that are not available in
the generic @ref MeshPrimitive enum, similarly see also the generic @relativeref{Magnum,MeshPrimitive} enum, similarly see also
@ref Trade-MeshAttributeData-custom-vertex-format for details on @ref Trade-MeshAttributeData-custom-vertex-format for details on
implementation-specific @ref VertexFormat values. implementation-specific @ref VertexFormat values.
@see @ref AbstractImporter::mesh() @see @ref AbstractImporter::mesh()

19
src/Magnum/Trade/imageconverter.cpp

@ -75,7 +75,8 @@ magnum-imageconverter [-h|--help] [-I|--importer IMPORTER]
Arguments: Arguments:
- `input` --- input image - `input` --- input image
- `output` --- output image, ignored if `--in-place` or `--info` is present - `output` --- output image; ignored if `--info` is present, disallowed for
`--in-place`
- `-h`, `--help` --- display this help message and exit - `-h`, `--help` --- display this help message and exit
- `-I`, `--importer IMPORTER` --- image importer plugin (default: - `-I`, `--importer IMPORTER` --- image importer plugin (default:
@ref Trade::AnyImageImporter "AnyImageImporter") @ref Trade::AnyImageImporter "AnyImageImporter")
@ -145,7 +146,7 @@ using namespace Magnum;
int main(int argc, char** argv) { int main(int argc, char** argv) {
Utility::Arguments args; Utility::Arguments args;
args.addArgument("input").setHelp("input", "input image") args.addArgument("input").setHelp("input", "input image")
.addArgument("output").setHelp("output", "output image, ignored if --in-place or --info is present") .addArgument("output").setHelp("output", "output image; ignored if --info is present, disallowed for --in-place")
.addOption('I', "importer", "AnyImageImporter").setHelp("importer", "image importer plugin") .addOption('I', "importer", "AnyImageImporter").setHelp("importer", "image importer plugin")
.addOption('C', "converter", "AnyImageConverter").setHelp("converter", "image converter plugin") .addOption('C', "converter", "AnyImageConverter").setHelp("converter", "image converter plugin")
.addOption("plugin-dir").setHelp("plugin-dir", "override base plugin dir", "DIR") .addOption("plugin-dir").setHelp("plugin-dir", "override base plugin dir", "DIR")
@ -182,6 +183,20 @@ plugin configuration. If the = character is omitted, it's equivalent to saying
key=true; configuration subgroups are delimited with /.)") key=true; configuration subgroups are delimited with /.)")
.parse(argc, argv); .parse(argc, argv);
/* Generic checks */
if(!args.value<Containers::StringView>("output").isEmpty()) {
if(args.isSet("in-place")) {
Error{} << "Output file shouldn't be set for --in-place:" << args.value<Containers::StringView>("output");
return 1;
}
/* Not an error in this case, it should be possible to just append
--info to existing command line without having to remove anything.
But print a warning at least, it could also be a mistyped option. */
if(args.isSet("info"))
Warning{} << "Ignoring output file for --info:" << args.value<Containers::StringView>("output");
}
PluginManager::Manager<Trade::AbstractImporter> importerManager{ PluginManager::Manager<Trade::AbstractImporter> importerManager{
args.value("plugin-dir").empty() ? std::string{} : args.value("plugin-dir").empty() ? std::string{} :
Utility::Directory::join(args.value("plugin-dir"), Trade::AbstractImporter::pluginSearchPaths()[0])}; Utility::Directory::join(args.value("plugin-dir"), Trade::AbstractImporter::pluginSearchPaths()[0])};

2
src/Magnum/Vk/CMakeLists.txt

@ -55,6 +55,7 @@ set(MagnumVk_GracefulAssert_SRCS
ImageView.cpp ImageView.cpp
Instance.cpp Instance.cpp
LayerProperties.cpp LayerProperties.cpp
Mesh.cpp
MeshLayout.cpp MeshLayout.cpp
Memory.cpp Memory.cpp
Pipeline.cpp Pipeline.cpp
@ -93,6 +94,7 @@ set(MagnumVk_HEADERS
LayerProperties.h LayerProperties.h
Memory.h Memory.h
MemoryAllocateInfo.h MemoryAllocateInfo.h
Mesh.h
MeshLayout.h MeshLayout.h
Pipeline.h Pipeline.h
PipelineLayout.h PipelineLayout.h

6
src/Magnum/Vk/CommandBuffer.cpp

@ -84,6 +84,12 @@ CommandBuffer& CommandBuffer::begin(const CommandBufferBeginInfo& info) {
} }
void CommandBuffer::end() { void CommandBuffer::end() {
/* Clear everything that is valid only for the duration of this command
buffer recording -- so when the user calls reset() and begin() again,
the old values are not preserved */
/** @todo do this on begin() too? */
_dynamicRasterizationStates = {};
MAGNUM_VK_INTERNAL_ASSERT_SUCCESS((**_device).EndCommandBuffer(_handle)); MAGNUM_VK_INTERNAL_ASSERT_SUCCESS((**_device).EndCommandBuffer(_handle));
} }

59
src/Magnum/Vk/CommandBuffer.h

@ -31,6 +31,7 @@
*/ */
#include <initializer_list> #include <initializer_list>
#include <Corrade/Containers/BigEnumSet.h>
#include <Corrade/Containers/EnumSet.h> #include <Corrade/Containers/EnumSet.h>
#include "Magnum/Tags.h" #include "Magnum/Tags.h"
@ -259,6 +260,17 @@ class MAGNUM_VK_EXPORT CommandBuffer {
/** @brief Handle flags */ /** @brief Handle flags */
HandleFlags handleFlags() const { return _flags; } HandleFlags handleFlags() const { return _flags; }
/**
* @brief Dynamic states used by currently bound rasterization pipeline
*
* If no rasterization pipeline is bound or there are no dynamic states
* on the currently bound one, returns an empty set.
* @see @ref bindPipeline()
*/
DynamicRasterizationStates dynamicRasterizationStates() const {
return _dynamicRasterizationStates;
}
/** /**
* @brief Reset the command buffer * @brief Reset the command buffer
* *
@ -361,12 +373,24 @@ class MAGNUM_VK_EXPORT CommandBuffer {
* @brief Bind a pipeline * @brief Bind a pipeline
* @return Reference to self (for method chaining) * @return Reference to self (for method chaining)
* *
* Can be called both inside and outside a render pass. See * Can be called both inside and outside a render pass. If the pipeline
* @ref Vk-Pipeline-usage for a usage example. * is a rasterization pipeline, the set of its dynamic states is stored
* in @ref dynamicRasterizationStates() for use by drawing and other
* commands. See @ref Vk-Pipeline-usage for a usage example.
* @see @fn_vk_keyword{CmdBindPipeline} * @see @fn_vk_keyword{CmdBindPipeline}
*/ */
CommandBuffer& bindPipeline(Pipeline& pipeline); CommandBuffer& bindPipeline(Pipeline& pipeline);
/**
* @brief Draw a mesh
* @return Reference to self (for method chaining)
*
* Can be only called inside a render pass with a graphics pipeline
* bound. See @ref Vk-Mesh-drawing for a usage example.
* @see @fn_vk_keyword{CmdDraw}, @fn_vk_keyword{CmdDrawIndexed}
*/
CommandBuffer& draw(Mesh& mesh);
/** /**
* @brief Insert an execution barrier with optional memory dependencies * @brief Insert an execution barrier with optional memory dependencies
* @param sourceStages Source stages. Has to contain at least * @param sourceStages Source stages. Has to contain at least
@ -399,6 +423,33 @@ class MAGNUM_VK_EXPORT CommandBuffer {
* *
* See @ref Vk-Buffer-usage-copy, @ref Vk-Image-usage-clear and * See @ref Vk-Buffer-usage-copy, @ref Vk-Image-usage-clear and
* @ref Vk-Image-usage-copy for usage examples. * @ref Vk-Image-usage-copy for usage examples.
*
* @m_class{m-note m-success}
*
* @par
* Where possible, expressing the dependencies and image layout
* transitions via @ref RenderPass APIs is considered more
* efficient than an explicit barrier ([source](https://github.com/KhronosGroup/Vulkan-Docs/wiki/Synchronization-Examples#graphics-to-graphics-dependencies)).
* @par
* To avoid pipeline stalls and unnecessary synchronization, avoid
* using overly generic stage and access sets. On the other hand,
* using @ref PipelineStage::AllCommands together with
* @ref Access::MemoryRead / @relativeref{Access,MemoryWrite} is
* useful for debugging synchronization issues.
* @par
* According to multiple sources ([1](https://github.com/KhronosGroup/Vulkan-Docs/wiki/Synchronization-Examples#three-dispatches-first-dispatch-writes-to-one-storage-buffer-second-dispatch-writes-to-a-different-storage-buffer-third-dispatch-reads-both),
* [2](https://developer.nvidia.com/blog/vulkan-dos-donts/#h.6fr5jvul7u03)),
* it's advised to group multiple barriers together into a single
* call --- that way the worst case can be picked instead of
* sequentially going through all barriers.
* @par
* Even though it may seem counterintuitive, it's recommended
* ([1](https://github.com/KhronosGroup/Vulkan-Docs/wiki/Synchronization-Examples#three-dispatches-first-dispatch-writes-to-one-storage-buffer-second-dispatch-writes-to-a-different-storage-buffer-third-dispatch-reads-both),
* [2](http://themaister.net/blog/2019/08/14/yet-another-blog-explaining-vulkan-synchronization/))
* to do global memory barriers than per-resource barriers, except
* for cases where layout transition or queue ownership transfer
* needs to be done on a particular image or buffer.
*
* @see @fn_vk_keyword{CmdPipelineBarrier} * @see @fn_vk_keyword{CmdPipelineBarrier}
*/ */
CommandBuffer& pipelineBarrier(PipelineStages sourceStages, PipelineStages destinationStages, Containers::ArrayView<const MemoryBarrier> memoryBarriers, Containers::ArrayView<const BufferMemoryBarrier> bufferMemoryBarriers, Containers::ArrayView<const ImageMemoryBarrier> imageMemoryBarriers, DependencyFlags dependencyFlags = {}); CommandBuffer& pipelineBarrier(PipelineStages sourceStages, PipelineStages destinationStages, Containers::ArrayView<const MemoryBarrier> memoryBarriers, Containers::ArrayView<const BufferMemoryBarrier> bufferMemoryBarriers, Containers::ArrayView<const ImageMemoryBarrier> imageMemoryBarriers, DependencyFlags dependencyFlags = {});
@ -744,6 +795,9 @@ class MAGNUM_VK_EXPORT CommandBuffer {
MAGNUM_VK_LOCAL static void endRenderPassImplementationKHR(CommandBuffer& self, const VkSubpassEndInfo& endInfo); MAGNUM_VK_LOCAL static void endRenderPassImplementationKHR(CommandBuffer& self, const VkSubpassEndInfo& endInfo);
MAGNUM_VK_LOCAL static void endRenderPassImplementation12(CommandBuffer& self, const VkSubpassEndInfo& endInfo); MAGNUM_VK_LOCAL static void endRenderPassImplementation12(CommandBuffer& self, const VkSubpassEndInfo& endInfo);
MAGNUM_VK_LOCAL static void bindVertexBuffersImplementationDefault(CommandBuffer& self, UnsignedInt firstBinding, UnsignedInt bindingCount, const VkBuffer* buffers, const UnsignedLong* offsets, const UnsignedLong* strides);
MAGNUM_VK_LOCAL static void bindVertexBuffersImplementationEXT(CommandBuffer& self, UnsignedInt firstBinding, UnsignedInt bindingCount, const VkBuffer* buffers, const UnsignedLong* offsets, const UnsignedLong* strides);
MAGNUM_VK_LOCAL static void copyBufferImplementationDefault(CommandBuffer& self, const CopyBufferInfo& info); MAGNUM_VK_LOCAL static void copyBufferImplementationDefault(CommandBuffer& self, const CopyBufferInfo& info);
MAGNUM_VK_LOCAL static void copyBufferImplementationKHR(CommandBuffer& self, const CopyBufferInfo& info); MAGNUM_VK_LOCAL static void copyBufferImplementationKHR(CommandBuffer& self, const CopyBufferInfo& info);
@ -765,6 +819,7 @@ class MAGNUM_VK_EXPORT CommandBuffer {
VkCommandPool _pool; /* Used only for vkFreeCommandBuffers() */ VkCommandPool _pool; /* Used only for vkFreeCommandBuffers() */
VkCommandBuffer _handle; VkCommandBuffer _handle;
HandleFlags _flags; HandleFlags _flags;
DynamicRasterizationStates _dynamicRasterizationStates;
}; };
}} }}

12
src/Magnum/Vk/Device.cpp

@ -191,10 +191,12 @@ DeviceCreateInfo::DeviceCreateInfo(DeviceProperties& deviceProperties, const Ext
addEnabledExtensions<Extensions::KHR::create_renderpass2>(); addEnabledExtensions<Extensions::KHR::create_renderpass2>();
} }
/* Enable the KHR_copy_commands2 extension. Not in any Vulkan version /* Enable the KHR_copy_commands2 and EXT_extended_dynamic_state
yet. */ extensions. Not in any Vulkan version yet. */
if(extensionProperties->isSupported<Extensions::KHR::copy_commands2>()) if(extensionProperties->isSupported<Extensions::KHR::copy_commands2>())
addEnabledExtensions<Extensions::KHR::copy_commands2>(); addEnabledExtensions<Extensions::KHR::copy_commands2>();
if(extensionProperties->isSupported<Extensions::EXT::extended_dynamic_state>())
addEnabledExtensions<Extensions::EXT::extended_dynamic_state>();
/* Enable the KHR_portability_subset extension, which *has to be* /* Enable the KHR_portability_subset extension, which *has to be*
enabled when available. Not enabling any of its features though, enabled when available. Not enabling any of its features though,
@ -412,6 +414,8 @@ DeviceCreateInfo& DeviceCreateInfo::setEnabledFeatures(const DeviceFeatures& fea
_state->features.hostQueryReset, _state->features.hostQueryReset,
_state->features.indexTypeUint8, _state->features.indexTypeUint8,
_state->features.extendedDynamicState, _state->features.extendedDynamicState,
_state->features.robustness2,
_state->features.imageRobustness,
_state->features.rayTracingPipeline, _state->features.rayTracingPipeline,
_state->features.rayQuery _state->features.rayQuery
}); });
@ -533,6 +537,10 @@ DeviceCreateInfo& DeviceCreateInfo::setEnabledFeatures(const DeviceFeatures& fea
_state->features.indexTypeUint8, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES_EXT); _state->features.indexTypeUint8, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES_EXT);
structureConnectIfUsed(next, _state->firstEnabledFeature, structureConnectIfUsed(next, _state->firstEnabledFeature,
_state->features.extendedDynamicState, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT); _state->features.extendedDynamicState, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT);
structureConnectIfUsed(next, _state->firstEnabledFeature,
_state->features.robustness2, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT);
structureConnectIfUsed(next, _state->firstEnabledFeature,
_state->features.imageRobustness, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_ROBUSTNESS_FEATURES_EXT);
structureConnectIfUsed(next, _state->firstEnabledFeature, structureConnectIfUsed(next, _state->firstEnabledFeature,
_state->features.rayTracingPipeline, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR); _state->features.rayTracingPipeline, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR);
structureConnectIfUsed(next, _state->firstEnabledFeature, structureConnectIfUsed(next, _state->firstEnabledFeature,

4
src/Magnum/Vk/DeviceCreateInfo.h

@ -268,6 +268,10 @@ class MAGNUM_VK_EXPORT DeviceCreateInfo {
* (@vk_extension{EXT,index_type_uint8}) * (@vk_extension{EXT,index_type_uint8})
* - @type_vk_keyword{PhysicalDeviceExtendedDynamicStateFeaturesEXT} * - @type_vk_keyword{PhysicalDeviceExtendedDynamicStateFeaturesEXT}
* (@vk_extension{EXT,extended_dynamic_state}) * (@vk_extension{EXT,extended_dynamic_state})
* - @type_vk_keyword{PhysicalDeviceRobustness2FeaturesEXT}
* (@vk_extension{EXT,robustness2})
* - @type_vk_keyword{PhysicalDeviceImageRobustnessFeaturesEXT}
* (@vk_extension{EXT,image_robustness})
* - @type_vk_keyword{PhysicalDeviceRayTracingPipelineFeaturesKHR} * - @type_vk_keyword{PhysicalDeviceRayTracingPipelineFeaturesKHR}
* (@vk_extension{KHR,ray_tracing_pipeline}) * (@vk_extension{KHR,ray_tracing_pipeline})
* - @type_vk_keyword{PhysicalDeviceRayQueryFeaturesKHR} * - @type_vk_keyword{PhysicalDeviceRayQueryFeaturesKHR}

69
src/Magnum/Vk/DeviceFeatures.h

@ -59,18 +59,22 @@ usually just with the first letter uppercase instead of lowercase.
enum class DeviceFeature: UnsignedShort { enum class DeviceFeature: UnsignedShort {
/** /**
* Whether accesses to buffers are bounds-checked against the range of the * Whether accesses to buffers are bounds-checked against the range of the
* buffer descriptor. * buffer descriptor. Out of bounds accesses are guaranteed to not cause
* @todo expose @vk_extension{EXT,robustness2}, reference from here * application termination, but have implementation-dependent behavior. A
* subset of the guarantees provided by
* @ref DeviceFeature::RobustBufferAccess2.
* @see @ref DeviceFeature::RobustImageAccess,
* @relativeref{DeviceFeature,RobustImageAccess2}
*/ */
RobustBufferAccess, RobustBufferAccess,
/** /**
* Whether the full 32-bit range is supported for indexed draw calls when * Whether the full 32-bit range is supported for indexed draw calls when
* using @val_vk{INDEX_TYPE_UINT32,IndexType}. * using @ref MeshIndexType::UnsignedInt.
* @see @ref DeviceFeature::IndexTypeUint8 * @see @ref DeviceFeature::IndexTypeUnsignedByte
* @todo expose the `maxDrawIndexedIndexValue` limit * @todo expose the `maxDrawIndexedIndexValue` limit
*/ */
FullDrawIndexUint32, FullDrawIndexUnsignedInt,
/** /**
* Whether image views with @val_vk{IMAGE_VIEW_TYPE_CUBE_ARRAY,ImageViewType} * Whether image views with @val_vk{IMAGE_VIEW_TYPE_CUBE_ARRAY,ImageViewType}
@ -1267,17 +1271,17 @@ enum class DeviceFeature: UnsignedShort {
/** /**
* Whether an 8-bit type can be used in index buffers. * Whether an 8-bit type can be used in index buffers.
* @see @ref DeviceFeature::FullDrawIndexUint32 * @see @ref DeviceFeature::FullDrawIndexUnsignedInt
* @requires_vk_extension Extension @vk_extension{EXT,index_type_uint8} * @requires_vk_extension Extension @vk_extension{EXT,index_type_uint8}
*/ */
IndexTypeUint8, IndexTypeUnsignedByte,
/* VkPhysicalDeviceExtendedDynamicStateFeaturesEXT, #268 */ /* VkPhysicalDeviceExtendedDynamicStateFeaturesEXT, #268 */
/** /**
* The @ref DynamicRasterizationState::CullMode, * The @ref DynamicRasterizationState::CullMode,
* @relativeref{DynamicRasterizationState,FrontFace}, * @relativeref{DynamicRasterizationState,FrontFace},
* @relativeref{DynamicRasterizationState,PrimitiveTopology}, * @relativeref{DynamicRasterizationState,MeshPrimitive},
* @relativeref{DynamicRasterizationState,ViewportWithCount}, * @relativeref{DynamicRasterizationState,ViewportWithCount},
* @relativeref{DynamicRasterizationState,ScissorWithCount}, * @relativeref{DynamicRasterizationState,ScissorWithCount},
* @relativeref{DynamicRasterizationState,VertexInputBindingStride}, * @relativeref{DynamicRasterizationState,VertexInputBindingStride},
@ -1292,6 +1296,55 @@ enum class DeviceFeature: UnsignedShort {
*/ */
ExtendedDynamicState, ExtendedDynamicState,
/* VkPhysicalDeviceRobustness2FeaturesEXT, #287 */
/**
* Whether accesses to buffers are bounds-checked against the range of the
* buffer descriptor, rounded up to a multiple of buffer access size
* alignment. Out of bounds writes are guaranteed to be discarded, out of
* bounds reads are guaranteed to return zero values. A stricter but more
* expensive variant of @ref DeviceFeature::RobustBufferAccess.
* @requires_vk_extension Extension @vk_extension{EXT,robustness2}
* @todo expose the alignment limit
*/
RobustBufferAccess2,
/**
* Whether accesses to images are bounds-checked against the dimensions of
* the image view. Out of bounds loads are guaranteed to return zero
* values, with @f$ (0, 0, 0, 1) @f$ inserted for missing components based
* on the image format. A stricted but more expensive variant of
* @ref DeviceFeature::RobustImageAccess.
* @see @ref DeviceFeature::RobustBufferAccess,
* @relativeref{DeviceFeature,RobustBufferAccess2}
* @requires_vk_extension Extension @vk_extension{EXT,robustness2}
*/
RobustImageAccess2,
/**
* Whether descriptors can be written with a null resource or view handle,
* at which point they're considered valid and act as if the descriptor was
* bound to nothing.
* @requires_vk_extension Extension @vk_extension{EXT,robustness2}
*/
NullDescriptor,
/* VkPhysicalDeviceImageRobustnessFeaturesEXT, #336 */
/**
* Whether accesses to images are bounds-checked against the dimensions of
* the image view. Out of bounds loads are guaranteed to return zero
* values, with either @f$ (0, 0, 0, 0) @f$ or @f$ (0, 0, 0, 1) @f$
* inserted for missing components based on the image format, with no
* guarantees provided for values returned for an invalid mip level. A
* subset of the guarantees provided by
* @ref DeviceFeature::RobustImageAccess2.
* @see @ref DeviceFeature::RobustBufferAccess,
* @relativeref{DeviceFeature,RobustBufferAccess2}
* @requires_vk_extension Extension @vk_extension{EXT,image_robustness}
*/
RobustImageAccess,
/* VkPhysicalDeviceRayTracingPipelineFeaturesKHR, #348 */ /* VkPhysicalDeviceRayTracingPipelineFeaturesKHR, #348 */
/** /**

4
src/Magnum/Vk/DeviceProperties.cpp

@ -339,6 +339,10 @@ const DeviceFeatures& DeviceProperties::features() {
Implementation::structureConnect(next, features.indexTypeUint8, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES_EXT); Implementation::structureConnect(next, features.indexTypeUint8, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES_EXT);
if(isOrVersionSupportedInternal<Extensions::EXT::extended_dynamic_state>()) if(isOrVersionSupportedInternal<Extensions::EXT::extended_dynamic_state>())
Implementation::structureConnect(next, features.extendedDynamicState, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT); Implementation::structureConnect(next, features.extendedDynamicState, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT);
if(isOrVersionSupportedInternal<Extensions::EXT::robustness2>())
Implementation::structureConnect(next, features.robustness2, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT);
if(isOrVersionSupportedInternal<Extensions::EXT::image_robustness>())
Implementation::structureConnect(next, features.imageRobustness, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_ROBUSTNESS_FEATURES_EXT);
if(isOrVersionSupportedInternal<Extensions::KHR::ray_tracing_pipeline>()) if(isOrVersionSupportedInternal<Extensions::KHR::ray_tracing_pipeline>())
Implementation::structureConnect(next, features.rayTracingPipeline, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR); Implementation::structureConnect(next, features.rayTracingPipeline, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR);
if(isOrVersionSupportedInternal<Extensions::KHR::ray_query>()) if(isOrVersionSupportedInternal<Extensions::KHR::ray_query>())

10
src/Magnum/Vk/DeviceProperties.h

@ -161,10 +161,10 @@ Wraps a @type_vk_keyword{QueueFlagBits}.
@m_enum_values_as_keywords @m_enum_values_as_keywords
*/ */
enum class QueueFlag: UnsignedInt { enum class QueueFlag: UnsignedInt {
/** Supports graphics operations */ /** Supports general graphics and rasterization operations. */
Graphics = VK_QUEUE_GRAPHICS_BIT, Graphics = VK_QUEUE_GRAPHICS_BIT,
/** Supports compute operations */ /** Supports compute and ray tracing operations. */
Compute = VK_QUEUE_COMPUTE_BIT, Compute = VK_QUEUE_COMPUTE_BIT,
/** Supports transfer operations */ /** Supports transfer operations */
@ -492,6 +492,12 @@ class MAGNUM_VK_EXPORT DeviceProperties {
* - If the @vk_extension{EXT,extended_dynamic_state} extension is * - If the @vk_extension{EXT,extended_dynamic_state} extension is
* supported by the device, the `pNext` chain contains * supported by the device, the `pNext` chain contains
* @type_vk_keyword{PhysicalDeviceExtendedDynamicStateFeaturesEXT} * @type_vk_keyword{PhysicalDeviceExtendedDynamicStateFeaturesEXT}
* - If the @vk_extension{EXT,robustness2} extension is supported by
* the device, the `pNext` chain contains
* @type_vk_keyword{PhysicalDeviceRobustness2FeaturesEXT}
* - If the @vk_extension{EXT,image_robustness} extension is
* supported by the device, the `pNext` chain contains
* @type_vk_keyword{PhysicalDeviceImageRobustnessFeaturesEXT}
* - If the @vk_extension{KHR,ray_tracing_pipeline} extension is * - If the @vk_extension{KHR,ray_tracing_pipeline} extension is
* supported by the device, the `pNext` chain contains * supported by the device, the `pNext` chain contains
* @type_vk_keyword{PhysicalDeviceRayTracingPipelineFeaturesKHR} * @type_vk_keyword{PhysicalDeviceRayTracingPipelineFeaturesKHR}

24
src/Magnum/Vk/Enums.cpp

@ -32,6 +32,7 @@
#ifdef MAGNUM_BUILD_DEPRECATED #ifdef MAGNUM_BUILD_DEPRECATED
#include "Magnum/Vk/MeshLayout.h" #include "Magnum/Vk/MeshLayout.h"
#include "Magnum/Vk/Mesh.h"
#include "Magnum/Vk/PixelFormat.h" #include "Magnum/Vk/PixelFormat.h"
#include "Magnum/Vk/VertexFormat.h" #include "Magnum/Vk/VertexFormat.h"
#endif #endif
@ -40,12 +41,6 @@ namespace Magnum { namespace Vk {
namespace { namespace {
constexpr VkIndexType IndexTypeMapping[]{
VK_INDEX_TYPE_UINT8_EXT,
VK_INDEX_TYPE_UINT16,
VK_INDEX_TYPE_UINT32
};
constexpr VkFilter FilterMapping[]{ constexpr VkFilter FilterMapping[]{
VK_FILTER_NEAREST, VK_FILTER_NEAREST,
VK_FILTER_LINEAR VK_FILTER_LINEAR
@ -75,24 +70,15 @@ bool hasVkPrimitiveTopology(const Magnum::MeshPrimitive primitive) {
VkPrimitiveTopology vkPrimitiveTopology(const Magnum::MeshPrimitive primitive) { VkPrimitiveTopology vkPrimitiveTopology(const Magnum::MeshPrimitive primitive) {
return VkPrimitiveTopology(meshPrimitive(primitive)); return VkPrimitiveTopology(meshPrimitive(primitive));
} }
#endif
bool hasVkIndexType(const Magnum::MeshIndexType type) { bool hasVkIndexType(const Magnum::MeshIndexType) {
CORRADE_ASSERT(UnsignedInt(type) - 1 < Containers::arraySize(IndexTypeMapping), return true;
"Vk::hasVkIndexType(): invalid type" << type, {});
return UnsignedInt(IndexTypeMapping[UnsignedInt(type) - 1]) != ~UnsignedInt{};
} }
VkIndexType vkIndexType(const Magnum::MeshIndexType type) { VkIndexType vkIndexType(const Magnum::MeshIndexType type) {
CORRADE_ASSERT(UnsignedInt(type) - 1 < Containers::arraySize(IndexTypeMapping), return VkIndexType(meshIndexType(type));
"Vk::vkIndexType(): invalid type" << type, {});
const VkIndexType out = IndexTypeMapping[UnsignedInt(type) - 1];
CORRADE_ASSERT(out != VkIndexType(~UnsignedInt{}),
"Vk::vkIndexType(): unsupported type" << type, {});
return out;
} }
#ifdef MAGNUM_BUILD_DEPRECATED
bool hasVkFormat(const Magnum::VertexFormat format) { bool hasVkFormat(const Magnum::VertexFormat format) {
return hasVertexFormat(format); return hasVertexFormat(format);
} }
@ -104,9 +90,7 @@ bool hasVkFormat(const Magnum::PixelFormat format) {
bool hasVkFormat(const Magnum::CompressedPixelFormat format) { bool hasVkFormat(const Magnum::CompressedPixelFormat format) {
return hasPixelFormat(format); return hasPixelFormat(format);
} }
#endif
#ifdef MAGNUM_BUILD_DEPRECATED
VkFormat vkFormat(const Magnum::VertexFormat format) { VkFormat vkFormat(const Magnum::VertexFormat format) {
return VkFormat(vertexFormat(format)); return VkFormat(vertexFormat(format));
} }

28
src/Magnum/Vk/Enums.h

@ -51,33 +51,19 @@ CORRADE_DEPRECATED("use hasMeshPrimitive() instead") MAGNUM_VK_EXPORT bool hasVk
* @m_deprecated_since_latest Use @ref meshPrimitive() instead. * @m_deprecated_since_latest Use @ref meshPrimitive() instead.
*/ */
CORRADE_DEPRECATED("use meshPrimitive() instead") MAGNUM_VK_EXPORT VkPrimitiveTopology vkPrimitiveTopology(Magnum::MeshPrimitive primitive); CORRADE_DEPRECATED("use meshPrimitive() instead") MAGNUM_VK_EXPORT VkPrimitiveTopology vkPrimitiveTopology(Magnum::MeshPrimitive primitive);
#endif
/** /**
@brief Check availability of a generic index type * @brief Check availability of a generic index type
* @m_deprecated_since_latest All generic index types are available in Vulkan.
Returns @cpp false @ce if Vulkan doesn't support such type, @cpp true @ce
otherwise. The @p type value is expected to be valid.
@note Support of some types depends on presence of a particular Vulkan
extension. Such check is outside of the scope of this function and you are
expected to verify extension availability before using such type.
@see @ref vkIndexType(), @vk_extension{EXT,index_type_uint8}
*/ */
MAGNUM_VK_EXPORT bool hasVkIndexType(Magnum::MeshIndexType type); CORRADE_DEPRECATED("all generic index types are available in Vulkan") MAGNUM_VK_EXPORT bool hasVkIndexType(Magnum::MeshIndexType type);
/** /**
@brief Convert generic mesh index type to Vulkan mesh index type * @brief @copybrief meshIndexType()
* @m_deprecated_since_latest Use @ref meshIndexType() instead.
Not all generic index types have a Vulkan equivalent and this function expects
that given type is available. Use @ref hasVkIndexType() to query availability
of given index type.
@see @ref vkPrimitiveTopology()
*/ */
MAGNUM_VK_EXPORT VkIndexType vkIndexType(Magnum::MeshIndexType type); CORRADE_DEPRECATED("use meshIndexType() instead") MAGNUM_VK_EXPORT VkIndexType vkIndexType(Magnum::MeshIndexType type);
#ifdef MAGNUM_BUILD_DEPRECATED
/** /**
* @brief @copybrief hasVertexFormat() * @brief @copybrief hasVertexFormat()
* @m_deprecated_since_latest Use @ref hasVertexFormat() instead. * @m_deprecated_since_latest Use @ref hasVertexFormat() instead.
@ -97,9 +83,7 @@ CORRADE_DEPRECATED("use hasPixelFormat() instead") MAGNUM_VK_EXPORT bool hasVkFo
* instead. * instead.
*/ */
CORRADE_DEPRECATED("use hasPixelFormat() instead") MAGNUM_VK_EXPORT bool hasVkFormat(Magnum::CompressedPixelFormat format); CORRADE_DEPRECATED("use hasPixelFormat() instead") MAGNUM_VK_EXPORT bool hasVkFormat(Magnum::CompressedPixelFormat format);
#endif
#ifdef MAGNUM_BUILD_DEPRECATED
/** /**
* @brief @copybrief vertexFormat() * @brief @copybrief vertexFormat()
* @m_deprecated_since_latest Use @ref vertexFormat() instead. * @m_deprecated_since_latest Use @ref vertexFormat() instead.

2
src/Magnum/Vk/Extensions.cpp

@ -69,7 +69,9 @@ namespace {
constexpr Extension DeviceExtensions[] { constexpr Extension DeviceExtensions[] {
Extensions::EXT::debug_marker{}, Extensions::EXT::debug_marker{},
Extensions::EXT::extended_dynamic_state{}, Extensions::EXT::extended_dynamic_state{},
Extensions::EXT::image_robustness{},
Extensions::EXT::index_type_uint8{}, Extensions::EXT::index_type_uint8{},
Extensions::EXT::robustness2{},
Extensions::EXT::texture_compression_astc_hdr{}, Extensions::EXT::texture_compression_astc_hdr{},
Extensions::EXT::vertex_attribute_divisor{}, Extensions::EXT::vertex_attribute_divisor{},
Extensions::IMG::format_pvrtc{}, Extensions::IMG::format_pvrtc{},

2
src/Magnum/Vk/Extensions.h

@ -124,6 +124,8 @@ namespace EXT {
_extension(8, EXT,host_query_reset, Vk10, Vk12) // #262 _extension(8, EXT,host_query_reset, Vk10, Vk12) // #262
_extension(9, EXT,index_type_uint8, Vk10, None) // #266 _extension(9, EXT,index_type_uint8, Vk10, None) // #266
_extension(10, EXT,extended_dynamic_state, Vk10, None) // #268 _extension(10, EXT,extended_dynamic_state, Vk10, None) // #268
_extension(11, EXT,robustness2, Vk10, None) // #287
_extension(12, EXT,image_robustness, Vk10, None) // #336
} namespace IMG { } namespace IMG {
_extension(20, IMG,format_pvrtc, Vk10, None) // #55 _extension(20, IMG,format_pvrtc, Vk10, None) // #55
} namespace KHR { } namespace KHR {

2
src/Magnum/Vk/Implementation/DeviceFeatures.h

@ -61,6 +61,8 @@ struct DeviceFeatures {
VkPhysicalDeviceHostQueryResetFeatures hostQueryReset; VkPhysicalDeviceHostQueryResetFeatures hostQueryReset;
VkPhysicalDeviceIndexTypeUint8FeaturesEXT indexTypeUint8; VkPhysicalDeviceIndexTypeUint8FeaturesEXT indexTypeUint8;
VkPhysicalDeviceExtendedDynamicStateFeaturesEXT extendedDynamicState; VkPhysicalDeviceExtendedDynamicStateFeaturesEXT extendedDynamicState;
VkPhysicalDeviceRobustness2FeaturesEXT robustness2;
VkPhysicalDeviceImageRobustnessFeaturesEXT imageRobustness;
VkPhysicalDeviceRayTracingPipelineFeaturesKHR rayTracingPipeline; VkPhysicalDeviceRayTracingPipelineFeaturesKHR rayTracingPipeline;
VkPhysicalDeviceRayQueryFeaturesKHR rayQuery; VkPhysicalDeviceRayQueryFeaturesKHR rayQuery;
}; };

14
src/Magnum/Vk/Implementation/DeviceState.cpp

@ -32,6 +32,7 @@
#include "Magnum/Vk/Extensions.h" #include "Magnum/Vk/Extensions.h"
#include "Magnum/Vk/Image.h" #include "Magnum/Vk/Image.h"
#include "Magnum/Vk/RenderPass.h" #include "Magnum/Vk/RenderPass.h"
#include "Magnum/Vk/Shader.h"
#include "Magnum/Vk/Version.h" #include "Magnum/Vk/Version.h"
#include "Magnum/Vk/Implementation/DriverWorkaround.h" #include "Magnum/Vk/Implementation/DriverWorkaround.h"
@ -85,6 +86,12 @@ DeviceState::DeviceState(Device& device, Containers::Array<std::pair<Containers:
cmdEndRenderPassImplementation = &CommandBuffer::endRenderPassImplementationDefault; cmdEndRenderPassImplementation = &CommandBuffer::endRenderPassImplementationDefault;
} }
if(device.isExtensionEnabled<Extensions::EXT::extended_dynamic_state>()) {
cmdBindVertexBuffersImplementation = &CommandBuffer::bindVertexBuffersImplementationEXT;
} else {
cmdBindVertexBuffersImplementation = &CommandBuffer::bindVertexBuffersImplementationDefault;
}
if(device.isExtensionEnabled<Extensions::KHR::copy_commands2>()) { if(device.isExtensionEnabled<Extensions::KHR::copy_commands2>()) {
cmdCopyBufferImplementation = &CommandBuffer::copyBufferImplementationKHR; cmdCopyBufferImplementation = &CommandBuffer::copyBufferImplementationKHR;
cmdCopyImageImplementation = &CommandBuffer::copyImageImplementationKHR; cmdCopyImageImplementation = &CommandBuffer::copyImageImplementationKHR;
@ -107,6 +114,13 @@ DeviceState::DeviceState(Device& device, Containers::Array<std::pair<Containers:
cmdCopyImageToBufferImplementation = &CommandBuffer::copyImageToBufferImplementationDefault; cmdCopyImageToBufferImplementation = &CommandBuffer::copyImageToBufferImplementationDefault;
} }
} }
/* SPIR-V hotpatching on shader creation */
if(device.properties().name().hasPrefix("SwiftShader"_s) && !Implementation::isDriverWorkaroundDisabled(encounteredWorkarounds, "swiftshader-spirv-multi-entrypoint-conflicting-locations"_s)) {
createShaderImplementation = &Shader::createImplementationSwiftShaderMultiEntryPointPatching;
} else {
createShaderImplementation = &Shader::createImplementationDefault;
}
} }
}}} }}}

4
src/Magnum/Vk/Implementation/DeviceState.h

@ -56,6 +56,10 @@ struct DeviceState {
void(*cmdNextSubpassImplementation)(CommandBuffer&, const VkSubpassEndInfo&, const VkSubpassBeginInfo&); void(*cmdNextSubpassImplementation)(CommandBuffer&, const VkSubpassEndInfo&, const VkSubpassBeginInfo&);
void(*cmdEndRenderPassImplementation)(CommandBuffer&, const VkSubpassEndInfo&); void(*cmdEndRenderPassImplementation)(CommandBuffer&, const VkSubpassEndInfo&);
VkResult(*createShaderImplementation)(Device&, const VkShaderModuleCreateInfo&, const VkAllocationCallbacks*, VkShaderModule&);
void(*cmdBindVertexBuffersImplementation)(CommandBuffer&, UnsignedInt, UnsignedInt, const VkBuffer*, const UnsignedLong*, const UnsignedLong*);
void(*cmdCopyBufferImplementation)(CommandBuffer&, const CopyBufferInfo&); void(*cmdCopyBufferImplementation)(CommandBuffer&, const CopyBufferInfo&);
void(*cmdCopyImageImplementation)(CommandBuffer&, const CopyImageInfo&); void(*cmdCopyImageImplementation)(CommandBuffer&, const CopyImageInfo&);
void(*cmdCopyBufferToImageImplementation)(CommandBuffer&, const CopyBufferToImageInfo&); void(*cmdCopyBufferToImageImplementation)(CommandBuffer&, const CopyBufferToImageInfo&);

16
src/Magnum/Vk/Implementation/DriverWorkaround.cpp

@ -47,6 +47,22 @@ constexpr Containers::StringView KnownWorkarounds[]{
layer offset/count, but never in both, so the extra fields feel redundant. layer offset/count, but never in both, so the extra fields feel redundant.
Or maybe it's reserving space for layered 3D images? */ Or maybe it's reserving space for layered 3D images? */
"swiftshader-image-copy-extent-instead-of-layers"_s, "swiftshader-image-copy-extent-instead-of-layers"_s,
/* Multi-entrypoint SPIR-V modules that use the same location indices for
vertex outputs and fragment outputs (for example passing interpolated vertex
color through location 0 and having fragment output at location 0 as well)
will cause the fragment output to be always zero. Happens only when such a
multi-entrypoint SPIR-V module is used for the vertex shader, doesn't happen
with single-entrypoint modules. The fix is remapping the vertex/fragment
interface to not use the same location IDs as the fragment output. That
however causes SwiftShader to complain about zero format in the now-unused
location 0 such as
SwiftShader/src/Vulkan/VkFormat.cpp:1351 WARNING: UNSUPPORTED: Format: 0
SwiftShader/src/Pipeline/VertexRoutine.cpp:494 WARNING: UNSUPPORTED: stream.format 0
but apart from this noise everything works as expected. */
"swiftshader-spirv-multi-entrypoint-conflicting-locations"_s,
/* [workarounds] */ /* [workarounds] */
}; };

12
src/Magnum/Vk/Implementation/deviceFeatureMapping.hpp

@ -36,7 +36,7 @@
*/ */
#ifdef _c #ifdef _c
_c(RobustBufferAccess, robustBufferAccess) _c(RobustBufferAccess, robustBufferAccess)
_c(FullDrawIndexUint32, fullDrawIndexUint32) _c(FullDrawIndexUnsignedInt, fullDrawIndexUint32)
_c(ImageCubeArray, imageCubeArray) _c(ImageCubeArray, imageCubeArray)
_c(IndependentBlend, independentBlend) _c(IndependentBlend, independentBlend)
_c(GeometryShader, geometryShader) _c(GeometryShader, geometryShader)
@ -213,10 +213,18 @@ _ce(BufferDeviceAddressMultiDevice, bufferDeviceAddressMultiDevice)
_cext(HostQueryReset, hostQueryReset, hostQueryReset, EXT::host_query_reset) _cext(HostQueryReset, hostQueryReset, hostQueryReset, EXT::host_query_reset)
_cext(IndexTypeUint8, indexTypeUint8, indexTypeUint8, EXT::index_type_uint8) _cext(IndexTypeUnsignedByte, indexTypeUint8, indexTypeUint8, EXT::index_type_uint8)
_cext(ExtendedDynamicState, extendedDynamicState, extendedDynamicState, EXT::extended_dynamic_state) _cext(ExtendedDynamicState, extendedDynamicState, extendedDynamicState, EXT::extended_dynamic_state)
#define _ce(value, field) _cext(value, field, robustness2, EXT::robustness2)
_ce(RobustBufferAccess2, robustBufferAccess2)
_ce(RobustImageAccess2, robustImageAccess2)
_ce(NullDescriptor, nullDescriptor)
#undef _ce
_cext(RobustImageAccess, robustImageAccess, imageRobustness, EXT::image_robustness)
#define _ce(value, field) _cext(value, field, rayTracingPipeline, KHR::ray_tracing_pipeline) #define _ce(value, field) _cext(value, field, rayTracingPipeline, KHR::ray_tracing_pipeline)
_ce(RayTracingPipeline, rayTracingPipeline) _ce(RayTracingPipeline, rayTracingPipeline)
_ce(RayTracingPipelineShaderGroupHandleCaptureReplay, rayTracingPipelineShaderGroupHandleCaptureReplay) _ce(RayTracingPipelineShaderGroupHandleCaptureReplay, rayTracingPipelineShaderGroupHandleCaptureReplay)

2
src/Magnum/Vk/Implementation/dynamicRasterizationStateMapping.hpp

@ -36,7 +36,7 @@ _c(StencilWriteMask, STENCIL_WRITE_MASK)
_c(StencilReference, STENCIL_REFERENCE) _c(StencilReference, STENCIL_REFERENCE)
_c(CullMode, CULL_MODE_EXT) _c(CullMode, CULL_MODE_EXT)
_c(FrontFace, FRONT_FACE_EXT) _c(FrontFace, FRONT_FACE_EXT)
_c(PrimitiveTopology, PRIMITIVE_TOPOLOGY_EXT) _c(MeshPrimitive, PRIMITIVE_TOPOLOGY_EXT)
_c(ViewportWithCount, VIEWPORT_WITH_COUNT_EXT) _c(ViewportWithCount, VIEWPORT_WITH_COUNT_EXT)
_c(ScissorWithCount, SCISSOR_WITH_COUNT_EXT) _c(ScissorWithCount, SCISSOR_WITH_COUNT_EXT)
_c(VertexInputBindingStride, VERTEX_INPUT_BINDING_STRIDE_EXT) _c(VertexInputBindingStride, VERTEX_INPUT_BINDING_STRIDE_EXT)

116
src/Magnum/Vk/Implementation/spirvPatching.h

@ -0,0 +1,116 @@
#ifndef Magnum_Vk_Implementation_spirvPatching_h
#define Magnum_Vk_Implementation_spirvPatching_h
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021 Vladimír Vondruš <mosra@centrum.cz>
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.
*/
#include <Corrade/Containers/ArrayTuple.h>
#include "Magnum/Math/Functions.h"
/* All code in this file should be self-contained, with no link-time dependency
on ShaderTools */
#include "Magnum/ShaderTools/Implementation/spirv.h"
namespace Magnum { namespace Vk { namespace Implementation { namespace {
/* This kinda throws const-correctness through the window -- the the SPIR-V
utils work on const data, but this function actually does mutate them */
bool spirvPatchSwiftShaderConflictingMultiEntrypointLocations(Containers::ArrayView<const UnsignedInt> data) {
/* Find vertex/fragment entrypoints and count how many is there in total */
UnsignedInt entrypointCount = 0;
Containers::Optional<ShaderTools::Implementation::SpirvEntrypoint> vertexEntryPoint, fragmentEntryPoint;
while(Containers::Optional<ShaderTools::Implementation::SpirvEntrypoint> entrypoint = ShaderTools::Implementation::spirvNextEntrypoint(data)) {
++entrypointCount;
if(entrypoint->executionModel == SpvExecutionModelVertex)
vertexEntryPoint = *entrypoint;
else if(entrypoint->executionModel == SpvExecutionModelFragment)
fragmentEntryPoint = *entrypoint;
}
/* If there aren't both entrypoints, this bug doesn't affect the shader. If
there are more, we won't attempt anything -- right now SwiftShader
doesn't support geom/tess shaders, so the only possibility is that the
module is a library of multiple different vertex / fragment
implementations and that's too frightening as any patching would most
likely break things *really bad*. */
if(entrypointCount > 2 || !vertexEntryPoint || !fragmentEntryPoint)
return false;
/* Get locations and storage classes for all entrypoint interfaces */
Containers::ArrayView<ShaderTools::Implementation::SpirvEntrypointInterface> vertexInterface, fragmentInterface;
Containers::ArrayTuple interfaceData{
{Containers::ValueInit, vertexEntryPoint->interfaces.size(), vertexInterface},
{Containers::ValueInit, fragmentEntryPoint->interfaces.size(), fragmentInterface}
};
spirvEntrypointInterface(data, *vertexEntryPoint, vertexInterface);
spirvEntrypointInterface(data, *fragmentEntryPoint, fragmentInterface);
/* Calculate the max location so we know what to change to */
UnsignedInt maxLocation = 0;
for(const ShaderTools::Implementation::SpirvEntrypointInterface& i: vertexInterface)
if(i.location) maxLocation = Math::max(maxLocation, *i.location);
for(const ShaderTools::Implementation::SpirvEntrypointInterface& i: fragmentInterface)
if(i.location) maxLocation = Math::max(maxLocation, *i.location);
/* For all vertex outputs check if there are fragment outputs with the same
locations */
for(const ShaderTools::Implementation::SpirvEntrypointInterface& vertexOutput: vertexInterface) {
/* Ignore what's not an output or what doesn't have a location (for
example a builtin) */
if(!vertexOutput.storageClass || *vertexOutput.storageClass != SpvStorageClassOutput || !vertexOutput.location)
continue;
for(const ShaderTools::Implementation::SpirvEntrypointInterface& fragmentOutput: fragmentInterface) {
/* Ignore what's not an output or what doesn't have a location (for
example a builtin) */
if(!fragmentOutput.storageClass || *fragmentOutput.storageClass != SpvStorageClassOutput || !fragmentOutput.location) continue;
/* The same location used, we need to remap. Use the next highest
unused location and change also the corresponding fragment
input, if there's any. */
if(*vertexOutput.location == *fragmentOutput.location) {
const UnsignedInt newLocation = ++maxLocation;
for(const ShaderTools::Implementation::SpirvEntrypointInterface& fragmentInput: fragmentInterface) {
if(*fragmentInput.storageClass != SpvStorageClassInput)
continue;
if(*fragmentInput.location == *vertexOutput.location) {
/** @todo ugly! */
*const_cast<UnsignedInt*>(fragmentInput.location) = newLocation;
break;
}
}
/** @todo ugly! */
*const_cast<UnsignedInt*>(vertexOutput.location) = newLocation;
break;
}
}
}
return true;
}
}}}}
#endif

250
src/Magnum/Vk/Mesh.cpp

@ -0,0 +1,250 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021 Vladimír Vondruš <mosra@centrum.cz>
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.
*/
#include "Mesh.h"
#include "CommandBuffer.h"
#include <Corrade/Containers/Array.h>
#include "Magnum/Mesh.h"
#include "Magnum/Vk/Buffer.h"
#include "Magnum/Vk/Device.h"
#include "Magnum/Vk/MeshLayout.h"
#include "Magnum/Vk/RasterizationPipelineCreateInfo.h"
#include "Magnum/Vk/Implementation/DeviceState.h"
namespace Magnum { namespace Vk {
namespace {
constexpr MeshIndexType IndexTypeMapping[]{
MeshIndexType::UnsignedByte,
MeshIndexType::UnsignedShort,
MeshIndexType::UnsignedInt
};
}
MeshIndexType meshIndexType(const Magnum::MeshIndexType type) {
CORRADE_ASSERT(UnsignedInt(type) - 1 < Containers::arraySize(IndexTypeMapping),
"Vk::meshIndexType(): invalid type" << type, {});
return IndexTypeMapping[UnsignedInt(type) - 1];
}
Debug& operator<<(Debug& debug, const MeshIndexType value) {
debug << "Vk::MeshIndexType" << Debug::nospace;
switch(value) {
/* LCOV_EXCL_START */
#define _c(value) case Vk::MeshIndexType::value: return debug << "::" << Debug::nospace << #value;
_c(UnsignedByte)
_c(UnsignedShort)
_c(UnsignedInt)
#undef _c
/* LCOV_EXCL_STOP */
}
/* Vulkan docs have the values in decimal, so not converting to hex */
return debug << "(" << Debug::nospace << Int(value) << Debug::nospace << ")";
}
struct Mesh::State {
Containers::ArrayTuple vertexBufferData;
Containers::ArrayView<VkBuffer> vertexBuffers;
Containers::ArrayView<UnsignedLong> vertexBufferOffsets;
Containers::ArrayView<UnsignedLong> vertexBufferStrides;
Containers::ArrayView<Buffer> ownedVertexBuffers;
VkBuffer indexBuffer{};
Buffer ownedIndexBuffer{NoCreate};
UnsignedLong indexBufferOffset{};
MeshIndexType indexType{};
};
Mesh::Mesh(const MeshLayout& layout): Mesh{MeshLayout{layout.vkPipelineVertexInputStateCreateInfo(), layout.vkPipelineInputAssemblyStateCreateInfo()}} {}
Mesh::Mesh(MeshLayout&& layout): _layout{std::move(layout)} {
/* Since we know the count of buffer bindings, we can directly allocate
all needed memory upfront */
if(const UnsignedInt count = _layout.vkPipelineVertexInputStateCreateInfo().vertexBindingDescriptionCount) {
_state.emplace();
_state->vertexBufferData = Containers::ArrayTuple{
{Containers::ValueInit, count, _state->vertexBuffers},
{Containers::ValueInit, count, _state->vertexBufferOffsets},
{Containers::ValueInit, count, _state->vertexBufferStrides},
{Containers::NoInit, count, _state->ownedVertexBuffers}
};
/** @tod use DirectInit once ArrayTuple can do that */
for(Buffer& b: _state->ownedVertexBuffers) new(&b) Buffer{NoCreate};
}
}
/* We don't have any internal self-pointers so a default is fine, and
MeshLayout has its own constructor to handle those (if any) */
Mesh::Mesh(Mesh&&) noexcept = default;
Mesh::~Mesh() = default;
Mesh& Mesh::operator=(Mesh&&) noexcept = default;
UnsignedInt Mesh::count() const {
/* The inverse value is used to detect & assert on forgotten setCount()
in CommandBuffer::draw(), return 0 in that case as well */
return _count == ~UnsignedInt{} ? 0 : _count;
}
std::size_t Mesh::addVertexBufferInternal(UnsignedInt binding, VkBuffer buffer, UnsignedLong offset) {
/* Find this binding in the layout */
for(std::size_t i = 0, max = _layout.vkPipelineVertexInputStateCreateInfo().vertexBindingDescriptionCount; i != max; ++i) {
if(_layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[i].binding == binding) {
_state->vertexBuffers[i] = buffer;
_state->vertexBufferOffsets[i] = offset;
/* Save the stride as well in case a dynamic state would need it */
_state->vertexBufferStrides[i] = _layout.vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions[i].stride;
return i;
}
}
CORRADE_ASSERT_UNREACHABLE("Vk::Mesh::addVertexBuffer(): binding" << binding << "not present among" << _layout.vkPipelineVertexInputStateCreateInfo().vertexBindingDescriptionCount << "bindings in the layout", ~std::size_t{});
}
Mesh& Mesh::addVertexBuffer(const UnsignedInt binding, const VkBuffer buffer, const UnsignedLong offset) {
addVertexBufferInternal(binding, buffer, offset);
return *this;
}
Mesh& Mesh::addVertexBuffer(const UnsignedInt binding, Buffer&& buffer, const UnsignedLong offset) {
const std::size_t index = addVertexBufferInternal(binding, buffer, offset);
#ifdef CORRADE_GRACEFUL_ASSERT
if(index == ~std::size_t{}) return *this;
#endif
_state->ownedVertexBuffers[index] = std::move(buffer);
return *this;
}
Mesh& Mesh::setIndexBuffer(const VkBuffer buffer, const UnsignedLong offset, const MeshIndexType indexType) {
/* If the mesh has no vertex buffer bindings, the state isn't populated
in the constructor. Do it here. */
if(!_state) _state.emplace();
_state->indexBuffer = buffer;
_state->indexBufferOffset = offset;
_state->indexType = indexType;
return *this;
}
Mesh& Mesh::setIndexBuffer(const VkBuffer buffer, const UnsignedLong offset, const Magnum::MeshIndexType indexType) {
return setIndexBuffer(buffer, offset, meshIndexType(indexType));
}
Mesh& Mesh::setIndexBuffer(Buffer&& buffer, const UnsignedLong offset, const MeshIndexType indexType) {
setIndexBuffer(buffer, offset, indexType);
_state->ownedIndexBuffer = std::move(buffer);
return *this;
}
Mesh& Mesh::setIndexBuffer(Buffer&& buffer, const UnsignedLong offset, const Magnum::MeshIndexType indexType) {
return setIndexBuffer(std::move(buffer), offset, meshIndexType(indexType));
}
Containers::ArrayView<const VkBuffer> Mesh::vertexBuffers() {
return _state ? _state->vertexBuffers : nullptr;
}
Containers::ArrayView<const UnsignedLong> Mesh::vertexBufferOffsets() const {
return _state ? _state->vertexBufferOffsets : nullptr;
}
Containers::ArrayView<const UnsignedLong> Mesh::vertexBufferStrides() const {
return _state ? _state->vertexBufferStrides : nullptr;
}
bool Mesh::isIndexed() const { return _state && _state->indexBuffer; }
VkBuffer Mesh::indexBuffer() {
CORRADE_ASSERT(isIndexed(), "Vk::Mesh::indexBuffer(): the mesh is not indexed", {});
return _state->indexBuffer;
}
UnsignedLong Mesh::indexBufferOffset() const {
CORRADE_ASSERT(isIndexed(), "Vk::Mesh::indexBufferOffset(): the mesh is not indexed", {});
return _state->indexBufferOffset;
}
MeshIndexType Mesh::indexType() const {
CORRADE_ASSERT(isIndexed(), "Vk::Mesh::indexType(): the mesh is not indexed", {});
return _state->indexType;
}
CommandBuffer& CommandBuffer::draw(Mesh& mesh) {
CORRADE_ASSERT(mesh.isCountSet(),
"Vk::CommandBuffer::draw(): Mesh::setCount() was never called, probably a mistake?", *this);
if(!mesh.count() || !mesh.instanceCount()) return *this;
if(_dynamicRasterizationStates & DynamicRasterizationState::MeshPrimitive)
(**_device).CmdSetPrimitiveTopologyEXT(_handle, mesh.layout().vkPipelineInputAssemblyStateCreateInfo().topology);
const VkVertexInputBindingDescription* bindings = mesh.layout().vkPipelineVertexInputStateCreateInfo().pVertexBindingDescriptions;
for(std::size_t i = 0, max = mesh.layout().vkPipelineVertexInputStateCreateInfo().vertexBindingDescriptionCount; i != max; ++i) {
/** @todo don't call this for each binding, detect ranges */
_device->state().cmdBindVertexBuffersImplementation(*this,
bindings[i].binding, 1,
mesh.vertexBuffers() + i,
mesh.vertexBufferOffsets() + i,
_dynamicRasterizationStates & DynamicRasterizationState::VertexInputBindingStride ?
mesh.vertexBufferStrides() + i : nullptr
);
}
if(mesh.isIndexed()) {
(**_device).CmdBindIndexBuffer(_handle, mesh.indexBuffer(), mesh.indexBufferOffset(), VkIndexType(mesh.indexType()));
(**_device).CmdDrawIndexed(_handle, mesh.count(), mesh.instanceCount(), mesh.indexOffset(), mesh.vertexOffset(), mesh.instanceOffset());
} else {
(**_device).CmdDraw(_handle, mesh.count(), mesh.instanceCount(), mesh.vertexOffset(), mesh.instanceOffset());
}
return *this;
}
void CommandBuffer::bindVertexBuffersImplementationDefault(CommandBuffer& self, const UnsignedInt firstBinding, const UnsignedInt bindingCount, const VkBuffer* const buffers, const UnsignedLong* const offsets, const UnsignedLong* const strides) {
CORRADE_ASSERT(!strides,
"Vk::CommandBuffer::draw(): dynamic strides supplied for an implementation without extended dynamic state",
/* Calling this even in case the assert blows up to avoid validation
layer errors about unbound attributes when CORRADE_GRACEFUL_ASSERT
is enabled */
(**self._device).CmdBindVertexBuffers(self, firstBinding, bindingCount, buffers, offsets));
#ifdef CORRADE_NO_ASSERT
static_cast<void>(strides);
#endif
(**self._device).CmdBindVertexBuffers(self, firstBinding, bindingCount, buffers, offsets);
}
void CommandBuffer::bindVertexBuffersImplementationEXT(CommandBuffer& self, const UnsignedInt firstBinding, const UnsignedInt bindingCount, const VkBuffer* const buffers, const UnsignedLong* const offsets, const UnsignedLong* const strides) {
return (**self._device).CmdBindVertexBuffers2EXT(self, firstBinding, bindingCount, buffers, offsets, nullptr, strides);
}
}}

413
src/Magnum/Vk/Mesh.h

@ -0,0 +1,413 @@
#ifndef Magnum_Vk_Mesh_h
#define Magnum_Vk_Mesh_h
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021 Vladimír Vondruš <mosra@centrum.cz>
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.
*/
/** @file
* @brief Class @ref Magnum::Vk::Mesh, enum @ref Magnum::Vk::MeshIndexType, function @ref Magnum::Vk::meshIndexType()
* @m_since_latest
*/
#include <Corrade/Containers/Pointer.h>
#include "Magnum/Vk/MeshLayout.h"
namespace Magnum { namespace Vk {
/**
@brief Mesh index type
@m_since_latest
Wraps a @type_vk_keyword{IndexType}.
*/
enum class MeshIndexType: Int {
/**
* @ref Magnum::UnsignedByte "UnsignedByte".
*
* @m_class{m-note m-success}
*
* @par
* Discouraged on contemporary GPU architectures, prefer to use 16-bit
* indices instead.
*
* @requires_vk_feature @ref DeviceFeature::IndexTypeUnsignedByte
*/
UnsignedByte = VK_INDEX_TYPE_UINT8_EXT,
/** @ref Magnum::UnsignedShort "UnsignedShort" */
UnsignedShort = VK_INDEX_TYPE_UINT16,
/**
* @ref Magnum::UnsignedInt "UnsignedInt"
* @see @ref DeviceFeature::FullDrawIndexUnsignedInt
*/
UnsignedInt = VK_INDEX_TYPE_UINT32
};
/**
@debugoperatorenum{MeshIndexType}
@m_since_latest
*/
MAGNUM_VK_EXPORT Debug& operator<<(Debug& debug, MeshIndexType value);
/**
@brief Convert a generic index type to Vulkan index type
@m_since_latest
@see @ref meshPrimitive(), @ref vertexFormat()
*/
MAGNUM_VK_EXPORT MeshIndexType meshIndexType(Magnum::MeshIndexType type);
/**
@brief Mesh
@m_since_latest
Connects @ref MeshLayout with concrete vertex/index @ref Buffer instances and
manages related information such as vertex or instance count.
@section Vk-Mesh-populating Populating a mesh
Continuing from the @ref Vk-MeshLayout-usage "mesh layout setup", the @ref Mesh
gets concrete buffers bound using @ref addVertexBuffer() and vertex count
specified with @ref setCount():
@snippet MagnumVk.cpp Mesh-populating
For an indexed mesh, the index buffer can be specified via @ref setIndexBuffer().
With an index buffer present, @ref setCount() then describes count of indices,
instead of vertices:
@snippet MagnumVk.cpp Mesh-populating-indexed
@subsection Vk-Mesh-populating-owned Transferring buffer and layout ownership
To simplify resource management, it's possible to have the @ref Buffer
instances owned by the @ref Mesh, as well as the @ref MeshLayout, either using
@ref std::move() or by directly passing a r-value. If a single buffer is used
for multiple bindings (for example as both a vertex and an index buffer),
perform the move last:
@snippet MagnumVk.cpp Mesh-populating-owned
@section Vk-Mesh-drawing Drawing a mesh
Assuming a rasterization pipeline with the same @ref MeshLayout was bound, a
mesh can be then drawn using @ref CommandBuffer::draw(). The function takes
care of binding all buffers and executing an appropriate draw command:
@snippet MagnumVk.cpp Mesh-drawing
@subsection Vk-Mesh-drawing-dynamic Dynamic pipeline state
Both the @ref MeshPrimitive set in @ref MeshLayout constructor and binding
stride set in @ref MeshLayout::addBinding() can be set as dynamic in the
pipeline using @ref DynamicRasterizationState::MeshPrimitive and
@relativeref{DynamicRasterizationState,VertexInputBindingStride}, assuming
@ref DeviceFeature::ExtendedDynamicState is supported and enabled. The
@ref CommandBuffer::draw() function then checks what dynamic state is enabled
in the currently bound pipeline and implicitly sets all dynamic states.
Taking this to the extreme, with these two dynamic states and a dedicated
binding for each attribute you can make the pipeline accept basically any mesh
as long as just the attribute locations and types are the same --- offsets and
strided of particular attributes are then fully dynamic.
@snippet MagnumVk.cpp Mesh-drawing-dynamic
<b></b>
@m_class{m-note m-success}
@par
The performance aspect of this approach is a whole different topic, of
course. It isn't expected to be as fast as if the vertex and primitive
layout was fixed, but it *may* be faster than having to create and bind a
whole new pipeline several times over when drawing a large set of
layout-incompatible meshes.
*/
class MAGNUM_VK_EXPORT Mesh {
public:
/**
* @brief Construct with a reference to external @ref MeshLayout
*
* Assumes @p layout stays in scope for the whole lifetime of the
* @ref Mesh instance.
*/
explicit Mesh(const MeshLayout& layout);
/** @brief Construct with taking over @ref MeshLayout ownership */
explicit Mesh(MeshLayout&& layout);
/** @brief Copying is not allowed */
Mesh(const Mesh&) = delete;
/** @brief Move constructor */
Mesh(Mesh&&) noexcept;
/**
* @brief Destructor
*
* If any buffers were added using @ref addVertexBuffer(UnsignedInt, Buffer&&, UnsignedLong)
* or @ref setIndexBuffer(Buffer&&, UnsignedLong, MeshIndexType), their
* owned instanced are destructed at this point.
*/
~Mesh();
/** @brief Copying is not allowed */
Mesh& operator=(const Mesh&) = delete;
/** @brief Move assignment */
Mesh& operator=(Mesh&&) noexcept;
/** @brief Vertex/index count */
UnsignedInt count() const;
/**
* @brief Set vertex/index count
* @return Reference to self (for method chaining)
*
* If the mesh is indexed, the value is treated as index count,
* otherwise the value is vertex count. If set to @cpp 0 @ce, no draw
* commands are issued when calling @ref CommandBuffer::draw().
*
* @attention To prevent nothing being rendered by accident, this
* function has to be always called, even to just set the count to
* @cpp 0 @ce.
*
* @see @ref isIndexed(), @ref setInstanceCount(),
* @ref setVertexOffset(), @ref setIndexOffset()
*/
Mesh& setCount(UnsignedInt count) {
_count = count;
return *this;
}
/** @brief Vertex offset */
UnsignedInt vertexOffset() const { return _vertexOffset; }
/**
* @brief Set vertex offset
* @return Reference to self (for method chaining)
*
* For non-indexed meshes specifies the first vertex that will be
* drawn, for indexed meshes specifies the offset added to each index.
* Default is @cpp 0 @ce.
* @see @ref isIndexed(), @ref setIndexOffset(), @ref setCount()
*/
Mesh& setVertexOffset(UnsignedInt offset) {
_vertexOffset = offset;
return *this;
}
/** @brief Index offset */
UnsignedInt indexOffset() const { return _indexOffset; }
/**
* @brief Set index offset
* @return Reference to self (for method chaining)
*
* Expects that the mesh is indexed. Specifies the first index that
* will be drawn. Default is @cpp 0 @ce.
* @see @ref isIndexed(), @ref setVertexOffset(), @ref setCount()
*/
Mesh& setIndexOffset(UnsignedInt offset) {
_indexOffset = offset;
return *this;
}
/** @brief Instance count */
UnsignedInt instanceCount() const { return _instanceCount; }
/**
* @brief Set instance count
* @return Reference to self (for method chaining)
*
* If set to @cpp 0 @ce, no draw commands are issued when calling
* @ref CommandBuffer::draw(). Default is @cpp 1 @ce.
* @see @ref setCount(), @ref setInstanceOffset()
*/
Mesh& setInstanceCount(UnsignedInt count) {
_instanceCount = count;
return *this;
}
/** @brief Instance offset */
UnsignedInt instanceOffset() const { return _instanceOffset; }
/**
* @brief Set instance offset
* @return Reference to self (for method chaining)
*
* Specifies the first instance that will be drawn. Default is
* @cpp 0 @ce.
* @see @ref setInstanceCount()
*/
Mesh& setInstanceOffset(UnsignedInt offset) {
_instanceOffset = offset;
return *this;
}
/**
* @brief Add a vertex buffer
* @param binding Binding corresponding to a particular
* @ref MeshLayout::addBinding() call
* @param buffer A @ref Buffer instance or a raw Vulkan buffer
* handle
* @param offset Offset into the buffer, in bytes
* @return Reference to self (for method chaining)
*
* @see @ref setCount(), @ref setVertexOffset()
*/
Mesh& addVertexBuffer(UnsignedInt binding, VkBuffer buffer, UnsignedLong offset);
/**
* @brief Add a vertex buffer and take over its ownership
* @return Reference to self (for method chaining)
*
* Compared to @ref addVertexBuffer(UnsignedInt, VkBuffer, UnsignedLong)
* the @p buffer instance ownership is transferred to the class and
* thus doesn't have to be managed separately.
*/
Mesh& addVertexBuffer(UnsignedInt binding, Buffer&& buffer, UnsignedLong offset);
/**
* @brief Set an index buffer
* @param buffer A @ref Buffer instance or a raw Vulkan buffer
* handle
* @param offset Offset into the buffer, in bytes
* @param indexType Index type
* @return Reference to self (for method chaining)
*
* @see @ref setCount(), @ref setIndexOffset()
*/
Mesh& setIndexBuffer(VkBuffer buffer, UnsignedLong offset, MeshIndexType indexType);
/** @overload */
Mesh& setIndexBuffer(VkBuffer buffer, UnsignedLong offset, Magnum::MeshIndexType indexType);
/**
* @brief Set an index buffer and take over its ownership
* @return Reference to self (for method chaining)
*
* Compared to @ref setIndexBuffer(VkBuffer, UnsignedLong, MeshIndexType)
* the @p buffer instance ownership is transferred to the class and
* thus doesn't have to be managed separately.
*/
Mesh& setIndexBuffer(Buffer&& buffer, UnsignedLong offset, MeshIndexType indexType);
/** @overload */
Mesh& setIndexBuffer(Buffer&& buffer, UnsignedLong offset, Magnum::MeshIndexType indexType);
/** @brief Layout of this mesh */
const MeshLayout& layout() const { return _layout; }
/**
* @brief Vertex buffers
*
* Has the same length as the vertex buffer binding array in
* @ref layout(), the buffers correspond to binding IDs at the same
* index.
*/
Containers::ArrayView<const VkBuffer> vertexBuffers();
/**
* @brief Vertex buffer offsets
*
* Has the same length as the vertex buffer binding array in
* @ref layout(), offsets correspond to @ref vertexBuffers() at the
* same index.
*/
Containers::ArrayView<const UnsignedLong> vertexBufferOffsets() const;
/**
* @brief Vertex buffer strides
*
* Has the same length as the vertex buffer binding array in
* @ref layout(). The strides are the same as strides in the layout
* at the same index, but here in a form that's usable by
* @fn_vk{CmdBindVertexBuffers2} if
* @ref DynamicRasterizationState::VertexInputBindingStride is enabled.
*/
Containers::ArrayView<const UnsignedLong> vertexBufferStrides() const;
/**
* @brief Whether the mesh is indexed
*
* The mesh is considered indexed if @ref setIndexBuffer() was called.
*/
bool isIndexed() const;
/**
* @brief Index buffer
*
* Expects that the mesh is indexed.
* @see @ref isIndexed()
*/
VkBuffer indexBuffer();
/**
* @brief Index buffer offset
*
* Expects that the mesh is indexed.
* @see @ref isIndexed()
*/
UnsignedLong indexBufferOffset() const;
/**
* @brief Index type
*
* Expects that the mesh is indexed.
* @see @ref isIndexed()
*/
MeshIndexType indexType() const;
#ifdef DOXYGEN_GENERATING_OUTPUT
private:
#endif
#ifndef CORRADE_NO_ASSERT
/* Used by CommandBuffer::draw() for a sanity assert */
bool isCountSet() const { return _count != ~UnsignedInt{}; }
#endif
private:
/* This is all here and not in the State struct in order to avoid
unnecessary allocations for buffer-less meshes -- like with GL, we
want `draw(Mesh{MeshLayout{MeshPrimitive::Triangle}}.setCount(3))`
to be performant enough to not need to invent any alternatives. The
MeshLayout class does a similar thing. */
UnsignedInt _count = ~UnsignedInt{},
_vertexOffset = 0,
_indexOffset = 0,
_instanceCount = 1,
_instanceOffset = 0;
MeshLayout _layout;
MAGNUM_VK_LOCAL std::size_t addVertexBufferInternal(UnsignedInt binding, VkBuffer buffer, UnsignedLong offset);
struct State;
Containers::Pointer<State> _state;
};
}}
#endif

24
src/Magnum/Vk/MeshLayout.cpp

@ -172,7 +172,7 @@ bool MeshLayout::operator==(const MeshLayout& other) const {
#undef _c #undef _c
} }
MeshLayout& MeshLayout::addBinding(const UnsignedInt binding, const UnsignedInt stride) { MeshLayout& MeshLayout::addBinding(const UnsignedInt binding, const UnsignedInt stride) & {
if(!_state) _state.emplace(); if(!_state) _state.emplace();
/* Ensure order for efficient comparisons */ /* Ensure order for efficient comparisons */
@ -190,7 +190,11 @@ MeshLayout& MeshLayout::addBinding(const UnsignedInt binding, const UnsignedInt
return *this; return *this;
} }
MeshLayout& MeshLayout::addInstancedBinding(const UnsignedInt binding, const UnsignedInt stride, const UnsignedInt divisor) { MeshLayout&& MeshLayout::addBinding(const UnsignedInt binding, const UnsignedInt stride) && {
return std::move(addBinding(binding, stride));
}
MeshLayout& MeshLayout::addInstancedBinding(const UnsignedInt binding, const UnsignedInt stride, const UnsignedInt divisor) & {
if(!_state) _state.emplace(); if(!_state) _state.emplace();
/* Ensure order for efficient comparisons */ /* Ensure order for efficient comparisons */
@ -221,7 +225,11 @@ MeshLayout& MeshLayout::addInstancedBinding(const UnsignedInt binding, const Uns
return *this; return *this;
} }
MeshLayout& MeshLayout::addAttribute(const UnsignedInt location, const UnsignedInt binding, const VertexFormat format, const UnsignedInt offset) { MeshLayout&& MeshLayout::addInstancedBinding(const UnsignedInt binding, const UnsignedInt stride, const UnsignedInt divisor) && {
return std::move(addInstancedBinding(binding, stride, divisor));
}
MeshLayout& MeshLayout::addAttribute(const UnsignedInt location, const UnsignedInt binding, const VertexFormat format, const UnsignedInt offset) & {
if(!_state) _state.emplace(); if(!_state) _state.emplace();
/* Ensure order for efficient comparisons */ /* Ensure order for efficient comparisons */
@ -240,10 +248,18 @@ MeshLayout& MeshLayout::addAttribute(const UnsignedInt location, const UnsignedI
return *this; return *this;
} }
MeshLayout& MeshLayout::addAttribute(const UnsignedInt location, const UnsignedInt binding, const Magnum::VertexFormat format, const UnsignedInt offset) { MeshLayout&& MeshLayout::addAttribute(const UnsignedInt location, const UnsignedInt binding, const VertexFormat format, const UnsignedInt offset) && {
return std::move(addAttribute(location, binding, format, offset));
}
MeshLayout& MeshLayout::addAttribute(const UnsignedInt location, const UnsignedInt binding, const Magnum::VertexFormat format, const UnsignedInt offset) & {
return addAttribute(location, binding, vertexFormat(format), offset); return addAttribute(location, binding, vertexFormat(format), offset);
} }
MeshLayout&& MeshLayout::addAttribute(const UnsignedInt location, const UnsignedInt binding, const Magnum::VertexFormat format, const UnsignedInt offset) && {
return std::move(addAttribute(location, binding, format, offset));
}
Debug& operator<<(Debug& debug, const MeshPrimitive value) { Debug& operator<<(Debug& debug, const MeshPrimitive value) {
debug << "Vk::MeshPrimitive" << Debug::nospace; debug << "Vk::MeshPrimitive" << Debug::nospace;

30
src/Magnum/Vk/MeshLayout.h

@ -159,7 +159,7 @@ In case @ref isMeshPrimitiveImplementationSpecific() returns @cpp false @ce for
Not all generic mesh primitives have a Vulkan equivalent and this function Not all generic mesh primitives have a Vulkan equivalent and this function
expects that given primitive is available. Use @ref hasMeshPrimitive() to query expects that given primitive is available. Use @ref hasMeshPrimitive() to query
availability of given primitive. availability of given primitive.
@see @ref vkIndexType() @see @ref meshIndexType(), @ref vertexFormat()
*/ */
MAGNUM_VK_EXPORT MeshPrimitive meshPrimitive(Magnum::MeshPrimitive primitive); MAGNUM_VK_EXPORT MeshPrimitive meshPrimitive(Magnum::MeshPrimitive primitive);
@ -182,7 +182,7 @@ describing how vertex attributes are organized in buffers and what's the layout
of each attribute. Used as an input for creating a of each attribute. Used as an input for creating a
@ref Vk-Pipeline-creation-rasterization "rasterization pipeline". @ref Vk-Pipeline-creation-rasterization "rasterization pipeline".
@section Vk-MeshLayout-usage Usage @section Vk-MeshLayout-usage Mesh layout setup
As an example let's assume a shader expects positions, texture coordinates and As an example let's assume a shader expects positions, texture coordinates and
normals in locations @cpp 0 @ce, @cpp 1 @ce and @cpp 5 @ce, respectively. If normals in locations @cpp 0 @ce, @cpp 1 @ce and @cpp 5 @ce, respectively. If
@ -191,8 +191,9 @@ could look like this:
@snippet MagnumVk.cpp MeshLayout-usage @snippet MagnumVk.cpp MeshLayout-usage
The `BufferBinding` is then subsequently used as a binding index for a concrete The `Binding` is then subsequently used as a binding index for a concrete
vertex buffer when drawing. vertex buffer when drawing, which is described in the @ref Mesh class
documentation.
@subsection Vk-MeshLayout-usage-comparison Layout comparison @subsection Vk-MeshLayout-usage-comparison Layout comparison
@ -307,7 +308,9 @@ class MAGNUM_VK_EXPORT MeshLayout {
* *
* @see @ref addInstancedBinding() * @see @ref addInstancedBinding()
*/ */
MeshLayout& addBinding(UnsignedInt binding, UnsignedInt stride); MeshLayout& addBinding(UnsignedInt binding, UnsignedInt stride) &;
/** @overload */
MeshLayout&& addBinding(UnsignedInt binding, UnsignedInt stride) &&;
/** /**
* @brief Add an instanced buffer binding * @brief Add an instanced buffer binding
@ -344,7 +347,9 @@ class MAGNUM_VK_EXPORT MeshLayout {
* @requires_vk_feature @ref DeviceFeature::VertexAttributeInstanceRateZeroDivisor * @requires_vk_feature @ref DeviceFeature::VertexAttributeInstanceRateZeroDivisor
* if @p divisor is `0` * if @p divisor is `0`
*/ */
MeshLayout& addInstancedBinding(UnsignedInt binding, UnsignedInt stride, UnsignedInt divisor = 1); MeshLayout& addInstancedBinding(UnsignedInt binding, UnsignedInt stride, UnsignedInt divisor = 1) &;
/** @overload */
MeshLayout&& addInstancedBinding(UnsignedInt binding, UnsignedInt stride, UnsignedInt divisor = 1) &&;
/** /**
* @brief Add an attribute * @brief Add an attribute
@ -368,9 +373,13 @@ class MAGNUM_VK_EXPORT MeshLayout {
* - `format` * - `format`
* - `offset` * - `offset`
*/ */
MeshLayout& addAttribute(UnsignedInt location, UnsignedInt binding, VertexFormat format, UnsignedInt offset); MeshLayout& addAttribute(UnsignedInt location, UnsignedInt binding, VertexFormat format, UnsignedInt offset) &;
/** @overload */
MeshLayout&& addAttribute(UnsignedInt location, UnsignedInt binding, VertexFormat format, UnsignedInt offset) &&;
/** @overload */
MeshLayout& addAttribute(UnsignedInt location, UnsignedInt binding, Magnum::VertexFormat format, UnsignedInt offset) &;
/** @overload */ /** @overload */
MeshLayout& addAttribute(UnsignedInt location, UnsignedInt binding, Magnum::VertexFormat format, UnsignedInt offset); MeshLayout&& addAttribute(UnsignedInt location, UnsignedInt binding, Magnum::VertexFormat format, UnsignedInt offset) &&;
/** @brief Underlying @type_vk{PipelineVertexInputStateCreateInfo} structure */ /** @brief Underlying @type_vk{PipelineVertexInputStateCreateInfo} structure */
VkPipelineVertexInputStateCreateInfo& vkPipelineVertexInputStateCreateInfo() { VkPipelineVertexInputStateCreateInfo& vkPipelineVertexInputStateCreateInfo() {
@ -409,6 +418,11 @@ class MAGNUM_VK_EXPORT MeshLayout {
MAGNUM_VK_LOCAL bool hasNoExternalPointers() const; MAGNUM_VK_LOCAL bool hasNoExternalPointers() const;
#endif #endif
/* These are here instead of in the State struct in order to avoid
unnecessary allocations for buffer-less layouts -- like with GL, we
want `draw(Mesh{MeshLayout{MeshPrimitive::Triangle}}.setCount(3))`
to be performant enough to not need to invent any alternatives. The
Mesh class does a similar thing. */
VkPipelineVertexInputStateCreateInfo _vertexInfo; VkPipelineVertexInputStateCreateInfo _vertexInfo;
VkPipelineInputAssemblyStateCreateInfo _assemblyInfo; VkPipelineInputAssemblyStateCreateInfo _assemblyInfo;

72
src/Magnum/Vk/Pipeline.cpp

@ -29,6 +29,7 @@
#include "CommandBuffer.h" #include "CommandBuffer.h"
#include <Corrade/Containers/Array.h> #include <Corrade/Containers/Array.h>
#include <Corrade/Containers/BigEnumSet.hpp>
#include "Magnum/Vk/Assert.h" #include "Magnum/Vk/Assert.h"
#include "Magnum/Vk/Device.h" #include "Magnum/Vk/Device.h"
@ -42,7 +43,12 @@ namespace Magnum { namespace Vk {
struct RasterizationPipelineCreateInfo::State { struct RasterizationPipelineCreateInfo::State {
Containers::Array<VkPipelineColorBlendAttachmentState> colorBlendAttachments; Containers::Array<VkPipelineColorBlendAttachmentState> colorBlendAttachments;
Containers::Array<VkDynamicState> dynamicStates;
/* The enum is saved as well to be subsequently available through
Pipeline::dynamicRasterizationStates() */
DynamicRasterizationStates dynamicStates;
Containers::Array<VkDynamicState> dynamicStateList;
/** @todo make an array once we support multiview */ /** @todo make an array once we support multiview */
VkViewport viewport; VkViewport viewport;
VkRect2D scissor; VkRect2D scissor;
@ -241,21 +247,24 @@ constexpr VkDynamicState DynamicRasterizationStateMapping[]{
} }
RasterizationPipelineCreateInfo& RasterizationPipelineCreateInfo::setDynamicStates(const DynamicRasterizationStates& states) { RasterizationPipelineCreateInfo& RasterizationPipelineCreateInfo::setDynamicStates(const DynamicRasterizationStates& states) {
/* Save the enum so we can store it in the created Pipeline object later */
_state->dynamicStates = states;
/* Count the number of states set, allocate for that */ /* Count the number of states set, allocate for that */
std::size_t count = 0; std::size_t count = 0;
for(std::size_t i = 0; i != DynamicRasterizationStates::Size; ++i) for(std::size_t i = 0; i != DynamicRasterizationStates::Size; ++i)
count += Math::popcount(states.data()[i]); count += Math::popcount(states.data()[i]);
_state->dynamicStates = Containers::Array<VkDynamicState>{Containers::NoInit, count}; _state->dynamicStateList = Containers::Array<VkDynamicState>{Containers::NoInit, count};
std::size_t offset = 0; std::size_t offset = 0;
for(std::uint64_t i = 0; i != Containers::arraySize(DynamicRasterizationStateMapping); ++i) for(std::uint64_t i = 0; i != Containers::arraySize(DynamicRasterizationStateMapping); ++i)
if(states & DynamicRasterizationState(i)) if(states & DynamicRasterizationState(i))
_state->dynamicStates[offset++] = DynamicRasterizationStateMapping[i]; _state->dynamicStateList[offset++] = DynamicRasterizationStateMapping[i];
CORRADE_INTERNAL_ASSERT(offset == count); CORRADE_INTERNAL_ASSERT(offset == count);
_dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; _dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
_dynamicInfo.dynamicStateCount = count; _dynamicInfo.dynamicStateCount = count;
_dynamicInfo.pDynamicStates = _state->dynamicStates; _dynamicInfo.pDynamicStates = _state->dynamicStateList;
_info.pDynamicState = &_dynamicInfo; _info.pDynamicState = &_dynamicInfo;
return *this; return *this;
} }
@ -277,15 +286,20 @@ ComputePipelineCreateInfo::ComputePipelineCreateInfo(const VkComputePipelineCrea
member instead of doing a copy */ member instead of doing a copy */
_info(info) {} _info(info) {}
Pipeline Pipeline::wrap(Device& device, const PipelineBindPoint bindPoint, const VkPipeline handle, const HandleFlags flags) { Pipeline Pipeline::wrap(Device& device, const PipelineBindPoint bindPoint, const VkPipeline handle, const DynamicRasterizationStates& dynamicStates, const HandleFlags flags) {
Pipeline out{NoCreate}; Pipeline out{NoCreate};
out._device = &device; out._device = &device;
out._handle = handle; out._handle = handle;
out._bindPoint = bindPoint; out._bindPoint = bindPoint;
out._flags = flags; out._flags = flags;
out._dynamicStates.rasterization = dynamicStates;
return out; return out;
} }
Pipeline Pipeline::wrap(Device& device, const PipelineBindPoint bindPoint, const VkPipeline handle, const HandleFlags flags) {
return wrap(device, bindPoint, handle, DynamicRasterizationStates{}, flags);
}
Pipeline::Pipeline(Device& device, const RasterizationPipelineCreateInfo& info): Pipeline::Pipeline(Device& device, const RasterizationPipelineCreateInfo& info):
_device{&device}, _device{&device},
#ifdef CORRADE_GRACEFUL_ASSERT #ifdef CORRADE_GRACEFUL_ASSERT
@ -293,7 +307,8 @@ Pipeline::Pipeline(Device& device, const RasterizationPipelineCreateInfo& info):
_handle{}, _handle{},
#endif #endif
_bindPoint{PipelineBindPoint::Rasterization}, _bindPoint{PipelineBindPoint::Rasterization},
_flags{HandleFlag::DestroyOnDestruction} _flags{HandleFlag::DestroyOnDestruction},
_dynamicStates{info._state->dynamicStates}
{ {
/* Doesn't check that the viewport is really a dynamic state, but should /* Doesn't check that the viewport is really a dynamic state, but should
catch most cases without false positives */ catch most cases without false positives */
@ -303,13 +318,17 @@ Pipeline::Pipeline(Device& device, const RasterizationPipelineCreateInfo& info):
MAGNUM_VK_INTERNAL_ASSERT_SUCCESS(device->CreateGraphicsPipelines(device, {}, 1, info, nullptr, &_handle)); MAGNUM_VK_INTERNAL_ASSERT_SUCCESS(device->CreateGraphicsPipelines(device, {}, 1, info, nullptr, &_handle));
} }
Pipeline::Pipeline(Device& device, const ComputePipelineCreateInfo& info): _device{&device}, _bindPoint{PipelineBindPoint::Compute}, _flags{HandleFlag::DestroyOnDestruction} { Pipeline::Pipeline(Device& device, const ComputePipelineCreateInfo& info): _device{&device}, _bindPoint{PipelineBindPoint::Compute}, _flags{HandleFlag::DestroyOnDestruction}, _dynamicStates{} {
MAGNUM_VK_INTERNAL_ASSERT_SUCCESS(device->CreateComputePipelines(device, {}, 1, info, nullptr, &_handle)); MAGNUM_VK_INTERNAL_ASSERT_SUCCESS(device->CreateComputePipelines(device, {}, 1, info, nullptr, &_handle));
} }
Pipeline::Pipeline(NoCreateT): _device{}, _handle{}, _bindPoint{} {} Pipeline::Pipeline(NoCreateT): _device{}, _handle{}, _bindPoint{}, _dynamicStates{} {}
Pipeline::Pipeline(Pipeline&& other) noexcept: _device{other._device}, _handle{other._handle}, _bindPoint{other._bindPoint}, _flags{other._flags} { Pipeline::Pipeline(Pipeline&& other) noexcept: _device{other._device}, _handle{other._handle}, _bindPoint{other._bindPoint}, _flags{other._flags},
/* Can't use {} with GCC 4.8 here because it tries to initialize the first
member instead of doing a copy */
_dynamicStates(other._dynamicStates)
{
other._handle = {}; other._handle = {};
} }
@ -324,9 +343,16 @@ Pipeline& Pipeline::operator=(Pipeline&& other) noexcept {
swap(other._handle, _handle); swap(other._handle, _handle);
swap(other._bindPoint, _bindPoint); swap(other._bindPoint, _bindPoint);
swap(other._flags, _flags); swap(other._flags, _flags);
swap(other._dynamicStates, _dynamicStates);
return *this; return *this;
} }
DynamicRasterizationStates Pipeline::dynamicRasterizationStates() const {
CORRADE_ASSERT(_bindPoint == PipelineBindPoint::Rasterization,
"Vk::Pipeline::dynamicRasterizationStates(): not a rasterization pipeline", {});
return _dynamicStates.rasterization;
}
VkPipeline Pipeline::release() { VkPipeline Pipeline::release() {
const VkPipeline handle = _handle; const VkPipeline handle = _handle;
_handle = {}; _handle = {};
@ -386,6 +412,10 @@ ImageMemoryBarrier::ImageMemoryBarrier(const VkImageMemoryBarrier& barrier):
_barrier(barrier) {} _barrier(barrier) {}
CommandBuffer& CommandBuffer::bindPipeline(Pipeline& pipeline) { CommandBuffer& CommandBuffer::bindPipeline(Pipeline& pipeline) {
/* Save the set of dynamic states for future use */
if(pipeline.bindPoint() == PipelineBindPoint::Rasterization)
_dynamicRasterizationStates = pipeline.dynamicRasterizationStates();
(**_device).CmdBindPipeline(_handle, VkPipelineBindPoint(pipeline.bindPoint()), pipeline); (**_device).CmdBindPipeline(_handle, VkPipelineBindPoint(pipeline.bindPoint()), pipeline);
return *this; return *this;
} }
@ -451,4 +481,28 @@ Debug& operator<<(Debug& debug, const PipelineBindPoint value) {
return debug << "(" << Debug::nospace << Int(value) << Debug::nospace << ")"; return debug << "(" << Debug::nospace << Int(value) << Debug::nospace << ")";
} }
namespace {
constexpr const char* DynamicRasterizationStateNames[]{
#define _c(value, vkValue) #value,
#include "Magnum/Vk/Implementation/dynamicRasterizationStateMapping.hpp"
#undef _c
};
}
Debug& operator<<(Debug& debug, const DynamicRasterizationState value) {
debug << "Vk::DynamicRasterizationState" << Debug::nospace;
if(UnsignedInt(value) < Containers::arraySize(DynamicRasterizationStateNames)) {
return debug << "::" << Debug::nospace << DynamicRasterizationStateNames[UnsignedInt(value)];
}
return debug << "(" << Debug::nospace << reinterpret_cast<void*>(UnsignedByte(value)) << Debug::nospace << ")";
}
Debug& operator<<(Debug& debug, const DynamicRasterizationStates& value) {
return Containers::bigEnumSetDebugOutput(debug, value, "Vk::DynamicRasterizationStates{}");
}
}} }}

72
src/Magnum/Vk/Pipeline.h

@ -30,6 +30,7 @@
* @m_since_latest * @m_since_latest
*/ */
#include <Corrade/Containers/BigEnumSet.h>
#include <Corrade/Containers/EnumSet.h> #include <Corrade/Containers/EnumSet.h>
#include "Magnum/Magnum.h" #include "Magnum/Magnum.h"
@ -116,6 +117,9 @@ class MAGNUM_VK_EXPORT Pipeline {
* @param bindPoint Pipeline bind point. Available through * @param bindPoint Pipeline bind point. Available through
* @ref bindPoint() afterwards. * @ref bindPoint() afterwards.
* @param handle The @type_vk{Pipeline} handle * @param handle The @type_vk{Pipeline} handle
* @param dynamicStates Dynamic states enabled on the rasterization
* pipeline. Available through @ref dynamicRasterizationStates()
* afterwards.
* @param flags Handle flags * @param flags Handle flags
* *
* The @p handle is expected to be originating from @p device. Unlike * The @p handle is expected to be originating from @p device. Unlike
@ -124,6 +128,8 @@ class MAGNUM_VK_EXPORT Pipeline {
* different behavior. * different behavior.
* @see @ref release() * @see @ref release()
*/ */
static Pipeline wrap(Device& device, PipelineBindPoint bindPoint, VkPipeline handle, const DynamicRasterizationStates& dynamicStates, HandleFlags flags = {});
/** @overload */
static Pipeline wrap(Device& device, PipelineBindPoint bindPoint, VkPipeline handle, HandleFlags flags = {}); static Pipeline wrap(Device& device, PipelineBindPoint bindPoint, VkPipeline handle, HandleFlags flags = {});
/** /**
@ -193,6 +199,15 @@ class MAGNUM_VK_EXPORT Pipeline {
*/ */
PipelineBindPoint bindPoint() const { return _bindPoint; } PipelineBindPoint bindPoint() const { return _bindPoint; }
/**
* @brief Dynamic rasterization states enabled in this pipeline
*
* Contains the states passed to @ref RasterizationPipelineCreateInfo::setDynamicStates()
* or to the @ref wrap() call. Expects that @ref bindPoint() is
* @ref PipelineBindPoint::Rasterization.
*/
DynamicRasterizationStates dynamicRasterizationStates() const;
/** /**
* @brief Release the underlying Vulkan pipeline * @brief Release the underlying Vulkan pipeline
* *
@ -210,6 +225,9 @@ class MAGNUM_VK_EXPORT Pipeline {
VkPipeline _handle; VkPipeline _handle;
PipelineBindPoint _bindPoint; PipelineBindPoint _bindPoint;
HandleFlags _flags; HandleFlags _flags;
union {
DynamicRasterizationStates rasterization;
} _dynamicStates;
}; };
/** /**
@ -317,9 +335,9 @@ enum class PipelineStage: UnsignedInt {
RayTracingShader = VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, RayTracingShader = VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR,
/** /**
* Execution of all graphics stages. While numerically a single bit, it's * Execution of all rasterization stages. While numerically a single bit,
* equivalent to the logical OR of a supported and enabled subset of the * it's equivalent to the logical OR of a supported and enabled subset of
* following: * the following:
* *
* - @ref PipelineStage::DrawIndirect * - @ref PipelineStage::DrawIndirect
* - @ref PipelineStage::VertexInput * - @ref PipelineStage::VertexInput
@ -332,12 +350,23 @@ enum class PipelineStage: UnsignedInt {
* - @ref PipelineStage::LateFragmentTests * - @ref PipelineStage::LateFragmentTests
* - @ref PipelineStage::ColorAttachmentOutput * - @ref PipelineStage::ColorAttachmentOutput
* *
* Note that this *does not* include @ref PipelineStage::RayTracingShader * As the name suggests, this *does not* include
* or @ref PipelineStage::AccelerationStructureBuild. * @ref PipelineStage::RayTracingShader or
* @ref PipelineStage::AccelerationStructureBuild.
* @todo mention mesh / task shaders once exposed
*/ */
AllGraphics = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, AllRasterization = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
/** All commands */ /**
* All commands.
*
* @m_class{m-note m-success}
*
* @par
* To avoid pipeline stalls and unnecessary synchronization, it's not
* advised to use this flag except for debugging synchronization
* issues.
*/
AllCommands = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT AllCommands = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT
}; };
@ -505,6 +534,13 @@ enum class Access: UnsignedInt {
/** /**
* All read accesses. Valid for any @ref PipelineStage, treated as * All read accesses. Valid for any @ref PipelineStage, treated as
* equivalent of a combination of all `*Read` flags valid in given context. * equivalent of a combination of all `*Read` flags valid in given context.
*
* @m_class{m-note m-success}
*
* @par
* To avoid pipeline stalls and unnecessary synchronization, it's not
* advised to use this flag except for debugging synchronization
* issues.
*/ */
MemoryRead = VK_ACCESS_MEMORY_READ_BIT, MemoryRead = VK_ACCESS_MEMORY_READ_BIT,
@ -512,6 +548,13 @@ enum class Access: UnsignedInt {
* All write accesses. Valid for any @ref PipelineStage, treated as * All write accesses. Valid for any @ref PipelineStage, treated as
* equivalent of a combination of all `*Write` flags valid in given * equivalent of a combination of all `*Write` flags valid in given
* context. * context.
*
* @m_class{m-note m-success}
*
* @par
* To avoid pipeline stalls and unnecessary synchronization, it's not
* advised to use this flag except for debugging synchronization
* issues.
*/ */
MemoryWrite = VK_ACCESS_MEMORY_WRITE_BIT, MemoryWrite = VK_ACCESS_MEMORY_WRITE_BIT,
@ -596,7 +639,10 @@ class MAGNUM_VK_EXPORT MemoryBarrier {
/** /**
* @brief Constructor * @brief Constructor
* @param sourceAccesses Source memory access types * @param sourceAccesses Source memory access types
* participating in a dependency * participating in a dependency. While allowed, passing `Read`
* accesses here is redundant --- that's already taken care of by
* the stage execution dependency in
* @ref CommandBuffer::pipelineBarrier().
* @param destinationAccesses Destination memory access types * @param destinationAccesses Destination memory access types
* participating in a dependency * participating in a dependency
* *
@ -654,7 +700,10 @@ class MAGNUM_VK_EXPORT BufferMemoryBarrier {
/** /**
* @brief Constructor * @brief Constructor
* @param sourceAccesses Source memory access types * @param sourceAccesses Source memory access types
* participating in a dependency * participating in a dependency. While allowed, passing `Read`
* accesses here is redundant --- that's already taken care of by
* the stage execution dependency in
* @ref CommandBuffer::pipelineBarrier().
* @param destinationAccesses Destination memory access types * @param destinationAccesses Destination memory access types
* participating in a dependency * participating in a dependency
* @param buffer A @ref Buffer or a raw Vulkan buffer * @param buffer A @ref Buffer or a raw Vulkan buffer
@ -722,7 +771,10 @@ class MAGNUM_VK_EXPORT ImageMemoryBarrier {
/** /**
* @brief Constructor * @brief Constructor
* @param sourceAccesses Source memory access types * @param sourceAccesses Source memory access types
* participating in a dependency * participating in a dependency. While allowed, passing `Read`
* accesses here is redundant --- that's already taken care of by
* the stage execution dependency in
* @ref CommandBuffer::pipelineBarrier().
* @param oldLayout Old layout in an image layout * @param oldLayout Old layout in an image layout
* transition * transition
* @param destinationAccesses Destination memory access types * @param destinationAccesses Destination memory access types

25
src/Magnum/Vk/RasterizationPipelineCreateInfo.h

@ -159,12 +159,13 @@ enum class DynamicRasterizationState: UnsignedByte {
/** /**
* Only the @ref MeshPrimitive topology class set in @ref MeshLayout and * Only the @ref MeshPrimitive topology class set in @ref MeshLayout and
* passed to @ref RasterizationPipelineCreateInfo is used and the specific * passed to @ref RasterizationPipelineCreateInfo is used and the specific
* topology order and adjacency is expected to be set dynamically using * topology order and adjacency is expected to be set dynamically.
* @fn_vk{CmdSetPrimitiveTopologyEXT}. * @ref CommandBuffer::draw() does this automatically if a pipeline with
* this dynamic state is bound.
* @requires_vk_feature @ref DeviceFeature::ExtendedDynamicState * @requires_vk_feature @ref DeviceFeature::ExtendedDynamicState
* @m_keywords{VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT} * @m_keywords{VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT}
*/ */
PrimitiveTopology, MeshPrimitive,
/** /**
* Both the number of viewports and their ranges set in * Both the number of viewports and their ranges set in
@ -191,10 +192,10 @@ enum class DynamicRasterizationState: UnsignedByte {
/** /**
* Stride set in @ref MeshLayout::addBinding() and passed to * Stride set in @ref MeshLayout::addBinding() and passed to
* @ref RasterizationPipelineCreateInfo is ignored and expected to be set * @ref RasterizationPipelineCreateInfo is ignored and expected to be set
* dynamically using @fn_vk{CmdBindVertexBuffers2EXT} * dynamically. @ref CommandBuffer::draw() does this automatically if a
* pipeline with this dynamic state is bound.
* @requires_vk_feature @ref DeviceFeature::ExtendedDynamicState * @requires_vk_feature @ref DeviceFeature::ExtendedDynamicState
* @m_keywords{VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT} * @m_keywords{VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT}
* @todoc link to the actual API when exposed
*/ */
VertexInputBindingStride, VertexInputBindingStride,
@ -273,6 +274,18 @@ typedef Containers::BigEnumSet<DynamicRasterizationState, 1> DynamicRasterizatio
CORRADE_ENUMSET_OPERATORS(DynamicRasterizationStates) CORRADE_ENUMSET_OPERATORS(DynamicRasterizationStates)
/**
@debugoperatorenum{DynamicRasterizationState}
@m_since_latest
*/
MAGNUM_VK_EXPORT Debug& operator<<(Debug& debug, DynamicRasterizationState value);
/**
@debugoperatorenum{DynamicRasterizationState}
@m_since_latest
*/
MAGNUM_VK_EXPORT Debug& operator<<(Debug& debug, const DynamicRasterizationStates& value);
/** /**
@brief Rasterization pipeline creation info @brief Rasterization pipeline creation info
@m_since_latest @m_since_latest
@ -512,6 +525,8 @@ class MAGNUM_VK_EXPORT RasterizationPipelineCreateInfo {
operator const VkGraphicsPipelineCreateInfo*() const { return &_info; } operator const VkGraphicsPipelineCreateInfo*() const { return &_info; }
private: private:
friend Pipeline;
VkGraphicsPipelineCreateInfo _info; VkGraphicsPipelineCreateInfo _info;
VkPipelineViewportStateCreateInfo _viewportInfo; VkPipelineViewportStateCreateInfo _viewportInfo;
VkPipelineRasterizationStateCreateInfo _rasterizationInfo; VkPipelineRasterizationStateCreateInfo _rasterizationInfo;

39
src/Magnum/Vk/Shader.cpp

@ -26,11 +26,14 @@
#include "Shader.h" #include "Shader.h"
#include "ShaderCreateInfo.h" #include "ShaderCreateInfo.h"
#include <Corrade/Containers/ArrayView.h> #include <Corrade/Containers/Array.h>
#include <Corrade/Utility/Algorithms.h>
#include "Magnum/Vk/Assert.h" #include "Magnum/Vk/Assert.h"
#include "Magnum/Vk/Device.h" #include "Magnum/Vk/Device.h"
#include "Magnum/Vk/Handle.h" #include "Magnum/Vk/Handle.h"
#include "Magnum/Vk/Implementation/DeviceState.h"
#include "Magnum/Vk/Implementation/spirvPatching.h"
namespace Magnum { namespace Vk { namespace Magnum { namespace Vk {
@ -87,7 +90,7 @@ Shader Shader::wrap(Device& device, const VkShaderModule handle, const HandleFla
} }
Shader::Shader(Device& device, const ShaderCreateInfo& info): _device{&device}, _flags{HandleFlag::DestroyOnDestruction} { Shader::Shader(Device& device, const ShaderCreateInfo& info): _device{&device}, _flags{HandleFlag::DestroyOnDestruction} {
MAGNUM_VK_INTERNAL_ASSERT_SUCCESS(device->CreateShaderModule(device, info, nullptr, &_handle)); MAGNUM_VK_INTERNAL_ASSERT_SUCCESS(device.state().createShaderImplementation(device, *info, nullptr, _handle));
} }
Shader::Shader(NoCreateT): _device{}, _handle{} {} Shader::Shader(NoCreateT): _device{}, _handle{} {}
@ -115,4 +118,36 @@ VkShaderModule Shader::release() {
return handle; return handle;
} }
VkResult Shader::createImplementationDefault(Device& device, const VkShaderModuleCreateInfo& info, const VkAllocationCallbacks* callbacks, VkShaderModule& handle) {
return device->CreateShaderModule(device, &info, callbacks, &handle);
}
VkResult Shader::createImplementationSwiftShaderMultiEntryPointPatching(Device& device, const VkShaderModuleCreateInfo& info, const VkAllocationCallbacks* callbacks, VkShaderModule& handle) {
/* Can't use {} with GCC 4.8 here because it tries to initialize the first
member instead of doing a copy */
VkShaderModuleCreateInfo patchedInfo(info);
/** @todo there's too many casts and it's all slightly weird, figure out a
better API (the problem is that spirvData() skips the header and so we
can't use its output to copy anything anywhere, but even if we could
we'd need to call it again on the copied mutable data and that's even
worse than what's there now) */
/* Even though our ShaderCreateInfo *might* have the code owned and we thus
might not need to copy it, the owned code may also be read-only for
whatever reason (memory-mapped location etc). Thus, to prevent issues,
we go the safe route and copy always. */
Containers::Array<char> mutableCode{Containers::NoInit, info.codeSize};
Utility::copy(Containers::arrayView(reinterpret_cast<const char*>(info.pCode), info.codeSize), mutableCode);
/* If the code looks like SPIR-V, patch it. If not, supply the original and
let SwiftShader deal with it. */
if(Containers::ArrayView<const UnsignedInt> spirv = ShaderTools::Implementation::spirvData(mutableCode, mutableCode.size())) {
Implementation::spirvPatchSwiftShaderConflictingMultiEntrypointLocations(spirv);
patchedInfo.pCode = reinterpret_cast<UnsignedInt*>(mutableCode.data());
}
return createImplementationDefault(device, patchedInfo, callbacks, handle);
}
}} }}

7
src/Magnum/Vk/Shader.h

@ -39,6 +39,8 @@
namespace Magnum { namespace Vk { namespace Magnum { namespace Vk {
namespace Implementation { struct DeviceState; }
/** /**
@brief Shader stage @brief Shader stage
@m_since_latest @m_since_latest
@ -203,6 +205,11 @@ class MAGNUM_VK_EXPORT Shader {
VkShaderModule release(); VkShaderModule release();
private: private:
friend Implementation::DeviceState;
MAGNUM_VK_LOCAL static VkResult createImplementationDefault(Device& device, const VkShaderModuleCreateInfo& info, const VkAllocationCallbacks* callbacks, VkShaderModule& handle);
MAGNUM_VK_LOCAL static VkResult createImplementationSwiftShaderMultiEntryPointPatching(Device& device, const VkShaderModuleCreateInfo& info, const VkAllocationCallbacks* callbacks, VkShaderModule& handle);
/* Can't be a reference because of the NoCreate constructor */ /* Can't be a reference because of the NoCreate constructor */
Device* _device; Device* _device;

53
src/Magnum/Vk/Test/CMakeLists.txt

@ -42,6 +42,7 @@ corrade_add_test(VkInstanceTest InstanceTest.cpp LIBRARIES MagnumVk)
corrade_add_test(VkIntegrationTest IntegrationTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkIntegrationTest IntegrationTest.cpp LIBRARIES MagnumVk)
corrade_add_test(VkLayerPropertiesTest LayerPropertiesTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkLayerPropertiesTest LayerPropertiesTest.cpp LIBRARIES MagnumVk)
corrade_add_test(VkMemoryTest MemoryTest.cpp LIBRARIES MagnumVkTestLib) corrade_add_test(VkMemoryTest MemoryTest.cpp LIBRARIES MagnumVkTestLib)
corrade_add_test(VkMeshTest MeshTest.cpp LIBRARIES MagnumVkTestLib)
corrade_add_test(VkMeshLayoutTest MeshLayoutTest.cpp LIBRARIES MagnumVkTestLib) corrade_add_test(VkMeshLayoutTest MeshLayoutTest.cpp LIBRARIES MagnumVkTestLib)
corrade_add_test(VkPipelineTest PipelineTest.cpp LIBRARIES MagnumVkTestLib) corrade_add_test(VkPipelineTest PipelineTest.cpp LIBRARIES MagnumVkTestLib)
corrade_add_test(VkPipelineLayoutTest PipelineLayoutTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkPipelineLayoutTest PipelineLayoutTest.cpp LIBRARIES MagnumVk)
@ -49,7 +50,10 @@ corrade_add_test(VkPixelFormatTest PixelFormatTest.cpp LIBRARIES MagnumVkTestLib
corrade_add_test(VkQueueTest QueueTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkQueueTest QueueTest.cpp LIBRARIES MagnumVk)
corrade_add_test(VkResultTest ResultTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkResultTest ResultTest.cpp LIBRARIES MagnumVk)
corrade_add_test(VkRenderPassTest RenderPassTest.cpp LIBRARIES MagnumVkTestLib) corrade_add_test(VkRenderPassTest RenderPassTest.cpp LIBRARIES MagnumVkTestLib)
corrade_add_test(VkShaderTest ShaderTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkShaderTest ShaderTest.cpp LIBRARIES MagnumVk)
target_include_directories(VkShaderTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>)
corrade_add_test(VkShaderSetTest ShaderSetTest.cpp LIBRARIES MagnumVkTestLib) corrade_add_test(VkShaderSetTest ShaderSetTest.cpp LIBRARIES MagnumVkTestLib)
corrade_add_test(VkVertexFormatTest VertexFormatTest.cpp LIBRARIES MagnumVkTestLib) corrade_add_test(VkVertexFormatTest VertexFormatTest.cpp LIBRARIES MagnumVkTestLib)
@ -140,6 +144,7 @@ set_target_properties(
VkIntegrationTest VkIntegrationTest
VkLayerPropertiesTest VkLayerPropertiesTest
VkMemoryTest VkMemoryTest
VkMeshTest
VkMeshLayoutTest VkMeshLayoutTest
VkPipelineTest VkPipelineTest
VkPipelineLayoutTest VkPipelineLayoutTest
@ -148,20 +153,41 @@ set_target_properties(
VkResultTest VkResultTest
VkRenderPassTest VkRenderPassTest
VkShaderTest VkShaderTest
VkShaderSetTest
VkStructureHelpersTest VkStructureHelpersTest
VkVersionTest VkVersionTest
VkVertexFormatTest VkVertexFormatTest
PROPERTIES FOLDER "Magnum/Vk/Test") PROPERTIES FOLDER "Magnum/Vk/Test")
if(BUILD_VK_TESTS) if(BUILD_VK_TESTS)
# Otherwise CMake complains that Corrade::PluginManager is not found, wtf
find_package(Corrade REQUIRED PluginManager)
if(CORRADE_TARGET_ANDROID) if(CORRADE_TARGET_ANDROID)
set(VK_TEST_DIR ".") set(VK_TEST_DIR ".")
else() else()
set(VK_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(VK_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR})
endif() endif()
# CMake before 3.8 has broken $<TARGET_FILE*> expressions for iOS (see
# https://gitlab.kitware.com/cmake/cmake/merge_requests/404) and since
# Corrade doesn't support dynamic plugins on iOS, this sorta works around
# that. Should be revisited when updating Travis to newer Xcode (xcode7.3
# has CMake 3.6).
if(NOT BUILD_PLUGINS_STATIC)
if(WITH_ANYIMAGEIMPORTER)
set(ANYIMAGEIMPORTER_PLUGIN_FILENAME $<TARGET_FILE:AnyImageImporter>)
endif()
if(WITH_TGAIMPORTER)
set(TGAIMPORTER_PLUGIN_FILENAME $<TARGET_FILE:TgaImporter>)
endif()
endif()
# First replace ${} variables, then $<> generator expressions
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake
${CMAKE_CURRENT_BINARY_DIR}/configure.h) ${CMAKE_CURRENT_BINARY_DIR}/configure.h.in)
file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/configure.h
INPUT ${CMAKE_CURRENT_BINARY_DIR}/configure.h.in)
corrade_add_test(VkBufferVkTest BufferVkTest.cpp LIBRARIES MagnumVkTestLib MagnumVulkanTester) corrade_add_test(VkBufferVkTest BufferVkTest.cpp LIBRARIES MagnumVkTestLib MagnumVulkanTester)
corrade_add_test(VkCommandBufferVkTest CommandBufferVkTest.cpp LIBRARIES MagnumVulkanTester) corrade_add_test(VkCommandBufferVkTest CommandBufferVkTest.cpp LIBRARIES MagnumVulkanTester)
@ -176,17 +202,37 @@ if(BUILD_VK_TESTS)
corrade_add_test(VkImageViewVkTest ImageViewVkTest.cpp LIBRARIES MagnumVk MagnumVulkanTester) corrade_add_test(VkImageViewVkTest ImageViewVkTest.cpp LIBRARIES MagnumVk MagnumVulkanTester)
corrade_add_test(VkInstanceVkTest InstanceVkTest.cpp LIBRARIES MagnumVkTestLib) corrade_add_test(VkInstanceVkTest InstanceVkTest.cpp LIBRARIES MagnumVkTestLib)
corrade_add_test(VkMemoryVkTest MemoryVkTest.cpp LIBRARIES MagnumVk MagnumVulkanTester) corrade_add_test(VkMemoryVkTest MemoryVkTest.cpp LIBRARIES MagnumVk MagnumVulkanTester)
corrade_add_test(VkMeshVkTest MeshVkTest.cpp
LIBRARIES MagnumVkTestLib MagnumDebugTools MagnumVulkanTester
FILES
MeshTestFiles/flat.spv
MeshTestFiles/flat.tga
MeshTestFiles/noop.spv
MeshTestFiles/noop.tga
MeshTestFiles/vertexcolor.spv
MeshTestFiles/vertexcolor.tga)
target_include_directories(VkMeshVkTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>)
if(BUILD_PLUGINS_STATIC)
if(WITH_ANYIMAGEIMPORTER)
target_link_libraries(VkMeshVkTest PRIVATE AnyImageImporter)
endif()
if(WITH_TGAIMPORTER)
target_link_libraries(VkMeshVkTest PRIVATE TgaImporter)
endif()
endif()
corrade_add_test(VkPipelineVkTest PipelineVkTest.cpp corrade_add_test(VkPipelineVkTest PipelineVkTest.cpp
LIBRARIES MagnumVkTestLib MagnumVulkanTester LIBRARIES MagnumVkTestLib MagnumVulkanTester
FILES triangle-shaders.spv compute-noop.spv) FILES triangle-shaders.spv compute-noop.spv)
target_include_directories(VkPipelineVkTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) target_include_directories(VkPipelineVkTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>)
corrade_add_test(VkPipelineLayoutVkTest PipelineLayoutVkTest.cpp LIBRARIES MagnumVk MagnumVulkanTester) corrade_add_test(VkPipelineLayoutVkTest PipelineLayoutVkTest.cpp LIBRARIES MagnumVk MagnumVulkanTester)
corrade_add_test(VkQueueVkTest QueueVkTest.cpp LIBRARIES MagnumVk MagnumVulkanTester) corrade_add_test(VkQueueVkTest QueueVkTest.cpp LIBRARIES MagnumVk MagnumVulkanTester)
corrade_add_test(VkRenderPassVkTest RenderPassVkTest.cpp LIBRARIES MagnumVkTestLib MagnumVulkanTester) corrade_add_test(VkRenderPassVkTest RenderPassVkTest.cpp LIBRARIES MagnumVkTestLib MagnumVulkanTester)
corrade_add_test(VkShaderVkTest ShaderVkTest.cpp corrade_add_test(VkShaderVkTest ShaderVkTest.cpp
LIBRARIES MagnumVk MagnumVulkanTester LIBRARIES MagnumVk MagnumVulkanTester
FILES triangle-shaders.spv) FILES triangle-shaders.spv)
target_include_directories(VkShaderVkTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) target_include_directories(VkShaderVkTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>)
corrade_add_test(VkVersionVkTest VersionVkTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkVersionVkTest VersionVkTest.cpp LIBRARIES MagnumVk)
set_target_properties( set_target_properties(
@ -203,6 +249,7 @@ if(BUILD_VK_TESTS)
VkImageViewVkTest VkImageViewVkTest
VkInstanceVkTest VkInstanceVkTest
VkMemoryVkTest VkMemoryVkTest
VkMeshVkTest
VkPipelineVkTest VkPipelineVkTest
VkPipelineLayoutVkTest VkPipelineLayoutVkTest
VkQueueVkTest VkQueueVkTest

8
src/Magnum/Vk/Test/DeviceFeaturesTest.cpp

@ -95,14 +95,14 @@ void DeviceFeaturesTest::mapping() {
void DeviceFeaturesTest::debugFeature() { void DeviceFeaturesTest::debugFeature() {
std::ostringstream out; std::ostringstream out;
Debug{&out} << DeviceFeature::FullDrawIndexUint32 << DeviceFeature::VulkanMemoryModel << DeviceFeature(0xab); Debug{&out} << DeviceFeature::FullDrawIndexUnsignedInt << DeviceFeature::VulkanMemoryModel << DeviceFeature(0xab);
CORRADE_COMPARE(out.str(), "Vk::DeviceFeature::FullDrawIndexUint32 Vk::DeviceFeature::VulkanMemoryModel Vk::DeviceFeature(0xab)\n"); CORRADE_COMPARE(out.str(), "Vk::DeviceFeature::FullDrawIndexUnsignedInt Vk::DeviceFeature::VulkanMemoryModel Vk::DeviceFeature(0xab)\n");
} }
void DeviceFeaturesTest::debugFeatures() { void DeviceFeaturesTest::debugFeatures() {
std::ostringstream out; std::ostringstream out;
Debug{&out} << (DeviceFeature::FullDrawIndexUint32|DeviceFeature::VulkanMemoryModel|DeviceFeature(0xab)|DeviceFeature(0xcc)) << DeviceFeatures{}; Debug{&out} << (DeviceFeature::FullDrawIndexUnsignedInt|DeviceFeature::VulkanMemoryModel|DeviceFeature(0xab)|DeviceFeature(0xcc)) << DeviceFeatures{};
CORRADE_COMPARE(out.str(), "Vk::DeviceFeature::FullDrawIndexUint32|Vk::DeviceFeature::VulkanMemoryModel|Vk::DeviceFeature(0xab)|Vk::DeviceFeature(0xcc) Vk::DeviceFeatures{}\n"); CORRADE_COMPARE(out.str(), "Vk::DeviceFeature::FullDrawIndexUnsignedInt|Vk::DeviceFeature::VulkanMemoryModel|Vk::DeviceFeature(0xab)|Vk::DeviceFeature(0xcc) Vk::DeviceFeatures{}\n");
} }
}}}} }}}}

5
src/Magnum/Vk/Test/DeviceVkTest.cpp

@ -163,7 +163,8 @@ struct {
"Device: {}\n" "Device: {}\n"
"Device version: Vulkan {}.{}{}\n" "Device version: Vulkan {}.{}{}\n"
"Using device driver workarounds:\n" "Using device driver workarounds:\n"
" swiftshader-image-copy-extent-instead-of-layers\n"}, " swiftshader-image-copy-extent-instead-of-layers\n"
" swiftshader-spirv-multi-entrypoint-conflicting-locations\n"},
/* Shouldn't print anything if quiet output is enabled */ /* Shouldn't print anything if quiet output is enabled */
{"quiet", true, {"quiet", true,
Containers::array({"", Containers::array({"",
@ -171,7 +172,7 @@ struct {
""}, ""},
{"disabled workarounds", true, {"disabled workarounds", true,
Containers::array({"", Containers::array({"",
"--magnum-disable-workarounds", "swiftshader-image-copy-extent-instead-of-layers"}), "--magnum-disable-workarounds", "swiftshader-image-copy-extent-instead-of-layers swiftshader-spirv-multi-entrypoint-conflicting-locations"}),
"Device: {}\n" "Device: {}\n"
"Device version: Vulkan {}.{}{}\n"} "Device version: Vulkan {}.{}{}\n"}
}; };

78
src/Magnum/Vk/Test/EnumsTest.cpp

@ -38,10 +38,6 @@ namespace Magnum { namespace Vk { namespace Test { namespace {
struct EnumsTest: TestSuite::Tester { struct EnumsTest: TestSuite::Tester {
explicit EnumsTest(); explicit EnumsTest();
void mapVkIndexType();
void mapVkIndexTypeUnsupported();
void mapVkIndexTypeInvalid();
void mapVkFilter(); void mapVkFilter();
void mapVkFilterInvalid(); void mapVkFilterInvalid();
@ -55,11 +51,7 @@ struct EnumsTest: TestSuite::Tester {
}; };
EnumsTest::EnumsTest() { EnumsTest::EnumsTest() {
addTests({&EnumsTest::mapVkIndexType, addTests({&EnumsTest::mapVkFilter,
&EnumsTest::mapVkIndexTypeUnsupported,
&EnumsTest::mapVkIndexTypeInvalid,
&EnumsTest::mapVkFilter,
&EnumsTest::mapVkFilterInvalid, &EnumsTest::mapVkFilterInvalid,
&EnumsTest::mapVkSamplerMipmapMode, &EnumsTest::mapVkSamplerMipmapMode,
@ -71,74 +63,6 @@ EnumsTest::EnumsTest() {
&EnumsTest::mapVkSamplerAddressModeInvalid}); &EnumsTest::mapVkSamplerAddressModeInvalid});
} }
void EnumsTest::mapVkIndexType() {
CORRADE_VERIFY(hasVkIndexType(Magnum::MeshIndexType::UnsignedShort));
CORRADE_COMPARE(vkIndexType(Magnum::MeshIndexType::UnsignedShort), VK_INDEX_TYPE_UINT16);
CORRADE_VERIFY(hasVkIndexType(Magnum::MeshIndexType::UnsignedInt));
CORRADE_COMPARE(vkIndexType(Magnum::MeshIndexType::UnsignedInt), VK_INDEX_TYPE_UINT32);
/* Ensure all generic index types are handled. This goes through the first
16 bits, which should be enough. Going through 32 bits takes 8 seconds,
too much. */
for(UnsignedInt i = 1; i <= 0xffff; ++i) {
const auto type = Magnum::MeshIndexType(i);
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic error "-Wswitch"
#endif
switch(type) {
#define _c(type) \
case Magnum::MeshIndexType::type: \
CORRADE_VERIFY(UnsignedInt(vkIndexType(Magnum::MeshIndexType::type)) >= 0); \
break;
#include "Magnum/Implementation/meshIndexTypeMapping.hpp"
#undef _c
}
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
}
}
void EnumsTest::mapVkIndexTypeUnsupported() {
#ifdef CORRADE_NO_ASSERT
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions");
#endif
#if 1
CORRADE_SKIP("All index formats are supported.");
#else
CORRADE_VERIFY(!hasVkIndexType(Magnum::MeshIndexType::UnsignedByte));
std::ostringstream out;
{
Error redirectError{&out};
vkIndexType(Magnum::MeshIndexType::UnsignedByte);
}
CORRADE_COMPARE(out.str(),
"Vk::vkIndexType(): unsupported type MeshIndexType::UnsignedByte\n");
#endif
}
void EnumsTest::mapVkIndexTypeInvalid() {
#ifdef CORRADE_NO_ASSERT
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions");
#endif
std::ostringstream out;
Error redirectError{&out};
hasVkIndexType(Magnum::MeshIndexType(0x0));
hasVkIndexType(Magnum::MeshIndexType(0x12));
vkIndexType(Magnum::MeshIndexType(0x0));
vkIndexType(Magnum::MeshIndexType(0x12));
CORRADE_COMPARE(out.str(),
"Vk::hasVkIndexType(): invalid type MeshIndexType(0x0)\n"
"Vk::hasVkIndexType(): invalid type MeshIndexType(0x12)\n"
"Vk::vkIndexType(): invalid type MeshIndexType(0x0)\n"
"Vk::vkIndexType(): invalid type MeshIndexType(0x12)\n");
}
void EnumsTest::mapVkFilter() { void EnumsTest::mapVkFilter() {
CORRADE_COMPARE(vkFilter(SamplerFilter::Nearest), VK_FILTER_NEAREST); CORRADE_COMPARE(vkFilter(SamplerFilter::Nearest), VK_FILTER_NEAREST);
CORRADE_COMPARE(vkFilter(SamplerFilter::Linear), VK_FILTER_LINEAR); CORRADE_COMPARE(vkFilter(SamplerFilter::Linear), VK_FILTER_LINEAR);

17
src/Magnum/Vk/Test/MeshLayoutTest.cpp

@ -57,6 +57,8 @@ struct MeshLayoutTest: TestSuite::Tester {
template<class T> void addAttribute(); template<class T> void addAttribute();
void addAttributeWrongOrder(); void addAttributeWrongOrder();
void rvalue();
void compare(); void compare();
void compareExternalPointers(); void compareExternalPointers();
@ -84,6 +86,8 @@ MeshLayoutTest::MeshLayoutTest() {
&MeshLayoutTest::addAttribute<Magnum::VertexFormat>, &MeshLayoutTest::addAttribute<Magnum::VertexFormat>,
&MeshLayoutTest::addAttributeWrongOrder, &MeshLayoutTest::addAttributeWrongOrder,
&MeshLayoutTest::rvalue,
&MeshLayoutTest::compare, &MeshLayoutTest::compare,
&MeshLayoutTest::compareExternalPointers, &MeshLayoutTest::compareExternalPointers,
@ -383,6 +387,19 @@ void MeshLayoutTest::addAttributeWrongOrder() {
"Vk::MeshLayout::addAttribute(): location 5 can't be ordered after 5\n"); "Vk::MeshLayout::addAttribute(): location 5 can't be ordered after 5\n");
} }
void MeshLayoutTest::rvalue() {
MeshLayout&& layout = MeshLayout{MeshPrimitive::TriangleFan}
.addBinding(0, 37)
.addInstancedBinding(1, 26)
.addAttribute(0, 0, VertexFormat{}, 0)
.addAttribute(1, 1, Magnum::VertexFormat::Vector2, 0);
/* Just to test something, main point is that the above compiles, links and
returns a &&. Can't test anything related to the contents because the
destructor gets called at the end of the expression. */
CORRADE_VERIFY(&layout);
}
void MeshLayoutTest::compare() { void MeshLayoutTest::compare() {
MeshLayout emptyTriangles1{MeshPrimitive::Triangles}; MeshLayout emptyTriangles1{MeshPrimitive::Triangles};
MeshLayout emptyTriangles2{MeshPrimitive::Triangles}; MeshLayout emptyTriangles2{MeshPrimitive::Triangles};

278
src/Magnum/Vk/Test/MeshTest.cpp

@ -0,0 +1,278 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021 Vladimír Vondruš <mosra@centrum.cz>
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.
*/
#include <sstream>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/TestSuite/Compare/Container.h>
#include <Corrade/Utility/DebugStl.h>
#include "Magnum/Mesh.h"
#include "Magnum/Vk/Buffer.h"
#include "Magnum/Vk/Device.h"
#include "Magnum/Vk/Mesh.h"
namespace Magnum { namespace Vk { namespace Test { namespace {
struct MeshTest: TestSuite::Tester {
explicit MeshTest();
void mapIndexType();
void mapIndexTypeInvalid();
void construct();
void countsOffsets();
void addVertexBuffer();
void addVertexBufferOwned();
void addVertexBufferNoSuchBinding();
template<class T> void setIndexBuffer();
template<class T> void setIndexBufferOwned();
void indexPropertiesNotIndexed();
void debugIndexType();
};
MeshTest::MeshTest() {
addTests({&MeshTest::mapIndexType,
&MeshTest::mapIndexTypeInvalid,
&MeshTest::construct,
&MeshTest::countsOffsets,
&MeshTest::addVertexBuffer,
&MeshTest::addVertexBufferOwned,
&MeshTest::addVertexBufferNoSuchBinding,
&MeshTest::setIndexBuffer<MeshIndexType>,
&MeshTest::setIndexBuffer<Magnum::MeshIndexType>,
&MeshTest::setIndexBufferOwned<MeshIndexType>,
&MeshTest::setIndexBufferOwned<Magnum::MeshIndexType>,
&MeshTest::indexPropertiesNotIndexed,
&MeshTest::debugIndexType});
}
template<class> struct IndexTypeTraits;
template<> struct IndexTypeTraits<MeshIndexType> {
static const char* name() { return "MeshIndexType"; }
};
template<> struct IndexTypeTraits<Magnum::MeshIndexType> {
static const char* name() { return "Magnum::MeshIndexType"; }
};
void MeshTest::mapIndexType() {
CORRADE_COMPARE(meshIndexType(Magnum::MeshIndexType::UnsignedByte), MeshIndexType::UnsignedByte);
CORRADE_COMPARE(meshIndexType(Magnum::MeshIndexType::UnsignedShort), MeshIndexType::UnsignedShort);
CORRADE_COMPARE(meshIndexType(Magnum::MeshIndexType::UnsignedInt), MeshIndexType::UnsignedInt);
}
void MeshTest::mapIndexTypeInvalid() {
#ifdef CORRADE_NO_ASSERT
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions");
#endif
std::ostringstream out;
Error redirectError{&out};
meshIndexType(Magnum::MeshIndexType(0x0));
meshIndexType(Magnum::MeshIndexType(0x12));
CORRADE_COMPARE(out.str(),
"Vk::meshIndexType(): invalid type MeshIndexType(0x0)\n"
"Vk::meshIndexType(): invalid type MeshIndexType(0x12)\n");
}
void MeshTest::construct() {
MeshLayout layout{MeshPrimitive::Triangles};
layout.vkPipelineVertexInputStateCreateInfo().sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2;
layout.vkPipelineInputAssemblyStateCreateInfo().sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2;
Mesh mesh{layout};
/* These should be copies of the original layout */
CORRADE_COMPARE(mesh.layout().vkPipelineVertexInputStateCreateInfo().sType, VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2);
CORRADE_COMPARE(mesh.layout().vkPipelineInputAssemblyStateCreateInfo().sType, VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2);
CORRADE_COMPARE(mesh.count(), 0);
CORRADE_COMPARE(mesh.vertexOffset(), 0);
CORRADE_COMPARE(mesh.indexOffset(), 0);
CORRADE_COMPARE(mesh.instanceCount(), 1);
CORRADE_COMPARE(mesh.instanceOffset(), 0);
CORRADE_VERIFY(mesh.vertexBuffers().empty());
CORRADE_VERIFY(mesh.vertexBufferOffsets().empty());
CORRADE_VERIFY(mesh.vertexBufferStrides().empty());
CORRADE_VERIFY(!mesh.isIndexed());
}
void MeshTest::countsOffsets() {
Mesh mesh{MeshLayout{MeshPrimitive::Triangles}};
mesh.setCount(15)
.setVertexOffset(3)
.setIndexOffset(5)
.setInstanceCount(7)
.setInstanceOffset(9);
CORRADE_COMPARE(mesh.count(), 15);
CORRADE_COMPARE(mesh.vertexOffset(), 3);
CORRADE_COMPARE(mesh.indexOffset(), 5);
CORRADE_COMPARE(mesh.instanceCount(), 7);
CORRADE_COMPARE(mesh.instanceOffset(), 9);
}
void MeshTest::addVertexBuffer() {
Mesh mesh{MeshLayout{MeshPrimitive::TriangleFan}
.addBinding(1, 2)
.addInstancedBinding(5, 3)
};
CORRADE_COMPARE_AS(mesh.vertexBuffers(), Containers::arrayView({
VkBuffer{}, VkBuffer{}
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(mesh.vertexBufferOffsets(), Containers::arrayView<UnsignedLong>({
0, 0
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(mesh.vertexBufferStrides(), Containers::arrayView<UnsignedLong>({
0, 0
}), TestSuite::Compare::Container);
mesh.addVertexBuffer(5, reinterpret_cast<VkBuffer>(0xdead), 15);
CORRADE_COMPARE_AS(mesh.vertexBuffers(), Containers::arrayView({
VkBuffer{}, reinterpret_cast<VkBuffer>(0xdead)
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(mesh.vertexBufferOffsets(), Containers::arrayView<UnsignedLong>({
0, 15
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(mesh.vertexBufferStrides(), Containers::arrayView<UnsignedLong>({
0, 3
}), TestSuite::Compare::Container);
mesh.addVertexBuffer(1, reinterpret_cast<VkBuffer>(0xbeef), 37);
CORRADE_COMPARE_AS(mesh.vertexBuffers(), Containers::arrayView({
reinterpret_cast<VkBuffer>(0xbeef), reinterpret_cast<VkBuffer>(0xdead)
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(mesh.vertexBufferOffsets(), Containers::arrayView<UnsignedLong>({
37, 15
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(mesh.vertexBufferStrides(), Containers::arrayView<UnsignedLong>({
2, 3
}), TestSuite::Compare::Container);
}
void MeshTest::addVertexBufferOwned() {
Mesh mesh{MeshLayout{MeshPrimitive::TriangleFan}
.addBinding(1, 2)
.addInstancedBinding(5, 3)
};
Device device{NoCreate};
Buffer a = Buffer::wrap(device, reinterpret_cast<VkBuffer>(0xdead));
Buffer b = Buffer::wrap(device, reinterpret_cast<VkBuffer>(0xbeef));
mesh.addVertexBuffer(5, std::move(a), 15)
.addVertexBuffer(1, std::move(b), 37);
CORRADE_VERIFY(!a.handle());
CORRADE_VERIFY(!b.handle());
CORRADE_COMPARE_AS(mesh.vertexBuffers(), Containers::arrayView({
reinterpret_cast<VkBuffer>(0xbeef), reinterpret_cast<VkBuffer>(0xdead)
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(mesh.vertexBufferOffsets(), Containers::arrayView<UnsignedLong>({
37, 15
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(mesh.vertexBufferStrides(), Containers::arrayView<UnsignedLong>({
2, 3
}), TestSuite::Compare::Container);
}
void MeshTest::addVertexBufferNoSuchBinding() {
#ifdef CORRADE_NO_ASSERT
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions");
#endif
Mesh noBindings{MeshLayout{MeshPrimitive::Triangles}};
Mesh differentBindings{MeshLayout{MeshPrimitive::Lines}
.addBinding(1, 2)
.addInstancedBinding(5, 3)};
std::ostringstream out;
Error redirectError{&out};
noBindings.addVertexBuffer(2, VkBuffer{}, 0);
differentBindings.addVertexBuffer(3, Buffer{NoCreate}, 5);
CORRADE_COMPARE(out.str(),
"Vk::Mesh::addVertexBuffer(): binding 2 not present among 0 bindings in the layout\n"
"Vk::Mesh::addVertexBuffer(): binding 3 not present among 2 bindings in the layout\n");
}
template<class T> void MeshTest::setIndexBuffer() {
setTestCaseTemplateName(IndexTypeTraits<T>::name());
Mesh mesh{MeshLayout{MeshPrimitive::Triangles}};
CORRADE_VERIFY(!mesh.isIndexed());
mesh.setIndexBuffer(reinterpret_cast<VkBuffer>(0xdead), 15, T::UnsignedByte);
CORRADE_VERIFY(mesh.isIndexed());
CORRADE_COMPARE(mesh.indexBuffer(), reinterpret_cast<VkBuffer>(0xdead));
CORRADE_COMPARE(mesh.indexBufferOffset(), 15);
CORRADE_COMPARE(mesh.indexType(), MeshIndexType::UnsignedByte);
}
template<class T> void MeshTest::setIndexBufferOwned() {
setTestCaseTemplateName(IndexTypeTraits<T>::name());
Device device{NoCreate};
Buffer a = Buffer::wrap(device, reinterpret_cast<VkBuffer>(0xdead));
Mesh mesh{MeshLayout{MeshPrimitive::Triangles}};
mesh.setIndexBuffer(std::move(a), 15, T::UnsignedByte);
CORRADE_VERIFY(!a.handle());
CORRADE_VERIFY(mesh.isIndexed());
CORRADE_COMPARE(mesh.indexBuffer(), reinterpret_cast<VkBuffer>(0xdead));
CORRADE_COMPARE(mesh.indexBufferOffset(), 15);
CORRADE_COMPARE(mesh.indexType(), MeshIndexType::UnsignedByte);
}
void MeshTest::indexPropertiesNotIndexed() {
#ifdef CORRADE_NO_ASSERT
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions");
#endif
Mesh mesh{MeshLayout{MeshPrimitive::Triangles}};
CORRADE_VERIFY(!mesh.isIndexed());
std::ostringstream out;
Error redirectError{&out};
mesh.indexBuffer();
mesh.indexBufferOffset();
mesh.indexType();
CORRADE_COMPARE(out.str(),
"Vk::Mesh::indexBuffer(): the mesh is not indexed\n"
"Vk::Mesh::indexBufferOffset(): the mesh is not indexed\n"
"Vk::Mesh::indexType(): the mesh is not indexed\n");
}
void MeshTest::debugIndexType() {
std::ostringstream out;
Debug{&out} << MeshIndexType::UnsignedShort << MeshIndexType(-10007655);
CORRADE_COMPARE(out.str(), "Vk::MeshIndexType::UnsignedShort Vk::MeshIndexType(-10007655)\n");
}
}}}}
CORRADE_TEST_MAIN(Magnum::Vk::Test::MeshTest)

5
src/Magnum/Vk/Test/MeshTestFiles/convert.sh

@ -0,0 +1,5 @@
#!/bin/bash
for i in $(ls *.spvasm); do
magnum-shaderconverter $i ${i%asm}
done

BIN
src/Magnum/Vk/Test/MeshTestFiles/flat.spv

Binary file not shown.

35
src/Magnum/Vk/Test/MeshTestFiles/flat.spvasm

@ -0,0 +1,35 @@
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %ver "ver" %position %gl_Position
OpEntryPoint Fragment %fra "fra" %fragmentColor
OpExecutionMode %fra OriginUpperLeft
OpDecorate %position Location 0
OpDecorate %gl_Position BuiltIn Position
OpDecorate %fragmentColor Location 0
%void = OpTypeVoid
%10 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Input_v4float = OpTypePointer Input %v4float
%position = OpVariable %_ptr_Input_v4float Input
%_ptr_Output_v4float = OpTypePointer Output %v4float
%gl_Position = OpVariable %_ptr_Output_v4float Output
%fragmentColor = OpVariable %_ptr_Output_v4float Output
%9 = OpConstant %float 0
%11 = OpConstant %float 1
%12 = OpConstantComposite %v4float %11 %9 %9 %11
%ver = OpFunction %void None %10
%ver_ = OpLabel
%16 = OpLoad %v4float %position
OpStore %gl_Position %16
OpReturn
OpFunctionEnd
%fra = OpFunction %void None %10
%fra_ = OpLabel
OpStore %fragmentColor %12
OpReturn
OpFunctionEnd

BIN
src/Magnum/Vk/Test/MeshTestFiles/flat.tga

Binary file not shown.

BIN
src/Magnum/Vk/Test/MeshTestFiles/noop.spv

Binary file not shown.

30
src/Magnum/Vk/Test/MeshTestFiles/noop.spvasm

@ -0,0 +1,30 @@
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %ver "ver" %gl_Position
OpEntryPoint Fragment %fra "fra" %fragmentColor
OpExecutionMode %fra OriginUpperLeft
OpDecorate %gl_Position BuiltIn Position
OpDecorate %fragmentColor Location 0
%void = OpTypeVoid
%10 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Output_v4float = OpTypePointer Output %v4float
%gl_Position = OpVariable %_ptr_Output_v4float Output
%fragmentColor = OpVariable %_ptr_Output_v4float Output
%9 = OpConstant %float 0
%11 = OpConstantComposite %v4float %9 %9 %9 %9
%ver = OpFunction %void None %10
%ver_ = OpLabel
OpStore %gl_Position %11
OpReturn
OpFunctionEnd
%fra = OpFunction %void None %10
%fra_ = OpLabel
OpStore %fragmentColor %11
OpReturn
OpFunctionEnd

BIN
src/Magnum/Vk/Test/MeshTestFiles/noop.tga

Binary file not shown.

BIN
src/Magnum/Vk/Test/MeshTestFiles/nullcolor.tga

Binary file not shown.

BIN
src/Magnum/Vk/Test/MeshTestFiles/vertexcolor.spv

Binary file not shown.

40
src/Magnum/Vk/Test/MeshTestFiles/vertexcolor.spvasm

@ -0,0 +1,40 @@
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %ver "ver" %position %color %gl_Position %interpolatedColorOut
OpEntryPoint Fragment %fra "fra" %interpolatedColorIn %fragmentColor
OpExecutionMode %fra OriginUpperLeft
OpDecorate %gl_Position BuiltIn Position
OpDecorate %position Location 0
OpDecorate %color Location 1
OpDecorate %interpolatedColorIn Location 0
OpDecorate %interpolatedColorOut Location 0
OpDecorate %fragmentColor Location 0
%void = OpTypeVoid
%10 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Input_v4float = OpTypePointer Input %v4float
%position = OpVariable %_ptr_Input_v4float Input
%color = OpVariable %_ptr_Input_v4float Input
%_ptr_Output_v4float = OpTypePointer Output %v4float
%gl_Position = OpVariable %_ptr_Output_v4float Output
%interpolatedColorOut = OpVariable %_ptr_Output_v4float Output
%interpolatedColorIn = OpVariable %_ptr_Input_v4float Input
%fragmentColor = OpVariable %_ptr_Output_v4float Output
%ver = OpFunction %void None %10
%ver_ = OpLabel
%16 = OpLoad %v4float %position
%17 = OpLoad %v4float %color
OpStore %gl_Position %16
OpStore %interpolatedColorOut %17
OpReturn
OpFunctionEnd
%fra = OpFunction %void None %10
%fra_ = OpLabel
%19 = OpLoad %v4float %interpolatedColorIn
OpStore %fragmentColor %19
OpReturn
OpFunctionEnd

BIN
src/Magnum/Vk/Test/MeshTestFiles/vertexcolor.tga

Binary file not shown.

878
src/Magnum/Vk/Test/MeshVkTest.cpp

@ -0,0 +1,878 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021 Vladimír Vondruš <mosra@centrum.cz>
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.
*/
#include <sstream>
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/StringView.h>
#include <Corrade/PluginManager/Manager.h>
#include <Corrade/Utility/Algorithms.h>
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/Directory.h>
#include "Magnum/ImageView.h"
#include "Magnum/PixelFormat.h"
#include "Magnum/DebugTools/CompareImage.h"
#include "Magnum/Math/Color.h"
#include "Magnum/Math/Range.h"
#include "Magnum/Trade/AbstractImporter.h"
#include "Magnum/Vk/BufferCreateInfo.h"
#include "Magnum/Vk/CommandBuffer.h"
#include "Magnum/Vk/CommandPoolCreateInfo.h"
#include "Magnum/Vk/DeviceCreateInfo.h"
#include "Magnum/Vk/DeviceFeatures.h"
#include "Magnum/Vk/DeviceProperties.h"
#include "Magnum/Vk/ExtensionProperties.h"
#include "Magnum/Vk/Extensions.h"
#include "Magnum/Vk/Fence.h"
#include "Magnum/Vk/FramebufferCreateInfo.h"
#include "Magnum/Vk/ImageCreateInfo.h"
#include "Magnum/Vk/ImageViewCreateInfo.h"
#include "Magnum/Vk/Mesh.h"
#include "Magnum/Vk/PipelineLayoutCreateInfo.h"
#include "Magnum/Vk/PixelFormat.h"
#include "Magnum/Vk/RasterizationPipelineCreateInfo.h"
#include "Magnum/Vk/RenderPassCreateInfo.h"
#include "Magnum/Vk/ShaderCreateInfo.h"
#include "Magnum/Vk/ShaderSet.h"
#include "Magnum/Vk/VertexFormat.h"
#include "Magnum/Vk/VulkanTester.h"
#include "configure.h"
namespace Magnum { namespace Vk { namespace Test { namespace {
struct MeshVkTest: VulkanTester {
explicit MeshVkTest();
void setup(Device& device);
void setup() { setup(device()); }
void setupRobustness2();
void setupExtendedDynamicState();
void teardown();
void cmdDraw();
void cmdDrawIndexed();
void cmdDrawTwoAttributes();
void cmdDrawTwoAttributesTwoBindings();
void cmdDrawNullBindingRobustness2();
void cmdDrawZeroCount();
void cmdDrawNoCountSet();
void cmdDrawDynamicPrimitive();
void cmdDrawDynamicStride();
void cmdDrawDynamicStrideInsufficientImplementation();
Queue _queue{NoCreate};
Device _deviceRobustness2{NoCreate}, _deviceExtendedDynamicState{NoCreate};
CommandPool _pool{NoCreate};
Image _color{NoCreate};
RenderPass _renderPass{NoCreate};
ImageView _colorView{NoCreate};
Framebuffer _framebuffer{NoCreate};
PipelineLayout _pipelineLayout{NoCreate};
Buffer _pixels{NoCreate};
private:
PluginManager::Manager<Trade::AbstractImporter> _manager{"nonexistent"};
};
using namespace Containers::Literals;
using namespace Math::Literals;
const struct {
const char* name;
UnsignedInt count;
UnsignedInt instanceCount;
} CmdDrawZeroCountData[] {
{"zero elements", 0, 1},
{"zero instances", 4, 0}
};
MeshVkTest::MeshVkTest() {
addTests({&MeshVkTest::cmdDraw,
&MeshVkTest::cmdDrawIndexed,
&MeshVkTest::cmdDrawTwoAttributes,
&MeshVkTest::cmdDrawTwoAttributesTwoBindings},
&MeshVkTest::setup,
&MeshVkTest::teardown);
addTests({&MeshVkTest::cmdDrawNullBindingRobustness2},
&MeshVkTest::setupRobustness2,
&MeshVkTest::teardown);
addInstancedTests({&MeshVkTest::cmdDrawZeroCount},
Containers::arraySize(CmdDrawZeroCountData),
&MeshVkTest::setup,
&MeshVkTest::teardown);
addTests({&MeshVkTest::cmdDrawNoCountSet},
&MeshVkTest::setup,
&MeshVkTest::teardown);
addTests({&MeshVkTest::cmdDrawDynamicPrimitive,
&MeshVkTest::cmdDrawDynamicStride},
&MeshVkTest::setupExtendedDynamicState,
&MeshVkTest::teardown);
addTests({&MeshVkTest::cmdDrawDynamicStrideInsufficientImplementation},
&MeshVkTest::setup,
&MeshVkTest::teardown);
/* Load the plugins directly from the build tree. Otherwise they're either
static and already loaded or not present in the build tree */
#ifdef ANYIMAGEIMPORTER_PLUGIN_FILENAME
CORRADE_INTERNAL_ASSERT_OUTPUT(_manager.load(ANYIMAGEIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded);
#endif
#ifdef TGAIMPORTER_PLUGIN_FILENAME
CORRADE_INTERNAL_ASSERT_OUTPUT(_manager.load(TGAIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded);
#endif
}
void MeshVkTest::setup(Device& device) {
_pool = CommandPool{device, CommandPoolCreateInfo{
device.properties().pickQueueFamily(QueueFlag::Graphics)}};
_color = Image{device, ImageCreateInfo2D{
ImageUsage::ColorAttachment|ImageUsage::TransferSource,
PixelFormat::RGBA8Srgb, {32, 32}, 1
}, Vk::MemoryFlag::DeviceLocal};
_renderPass = RenderPass{device, RenderPassCreateInfo{}
.setAttachments({AttachmentDescription{
_color.format(),
AttachmentLoadOperation::Clear,
AttachmentStoreOperation::Store,
ImageLayout::Undefined,
ImageLayout::TransferSource
}})
.addSubpass(SubpassDescription{}.setColorAttachments({
AttachmentReference{0, ImageLayout::ColorAttachment}
}))
/* So the color data are visible for the transfer */
.setDependencies({SubpassDependency{
0, SubpassDependency::External,
PipelineStage::ColorAttachmentOutput,
PipelineStage::Transfer,
Access::ColorAttachmentWrite,
Access::TransferRead
}})
};
_colorView = ImageView{device, ImageViewCreateInfo2D{_color}};
_framebuffer = Framebuffer{device, FramebufferCreateInfo{_renderPass, {
_colorView
}, {32, 32}}};
_pipelineLayout = PipelineLayout{device, PipelineLayoutCreateInfo{}};
_pixels = Buffer{device, BufferCreateInfo{
BufferUsage::TransferDestination, 32*32*4
}, Vk::MemoryFlag::HostVisible};
}
void MeshVkTest::setupRobustness2() {
DeviceProperties properties = pickDevice(instance());
/* If the extension / feature isn't supported, do nothing */
if(!properties.enumerateExtensionProperties().isSupported<Extensions::EXT::robustness2>() ||
!(properties.features() & DeviceFeature::NullDescriptor))
return;
/* Create the device only if not already, to avoid spamming the output */
if(!_deviceRobustness2.handle()) _deviceRobustness2.create(instance(), DeviceCreateInfo{std::move(properties)}
.addQueues(QueueFlag::Graphics, {0.0f}, {_queue})
.addEnabledExtensions<Extensions::EXT::robustness2>()
.setEnabledFeatures(DeviceFeature::NullDescriptor)
);
setup(_deviceRobustness2);
}
void MeshVkTest::setupExtendedDynamicState() {
DeviceProperties properties = pickDevice(instance());
/* If the extension / feature isn't supported, do nothing */
if(!properties.enumerateExtensionProperties().isSupported<Extensions::EXT::extended_dynamic_state>() ||
!(properties.features() & DeviceFeature::ExtendedDynamicState))
return;
/* Create the device only if not already, to avoid spamming the output */
if(!_deviceExtendedDynamicState.handle()) _deviceExtendedDynamicState.create(instance(), DeviceCreateInfo{std::move(properties)}
.addQueues(QueueFlag::Graphics, {0.0f}, {_queue})
.addEnabledExtensions<Extensions::EXT::extended_dynamic_state>()
.setEnabledFeatures(DeviceFeature::ExtendedDynamicState)
);
setup(_deviceExtendedDynamicState);
}
void MeshVkTest::teardown() {
_pool = CommandPool{NoCreate};
_renderPass = RenderPass{NoCreate};
_color = Image{NoCreate};
_colorView = ImageView{NoCreate};
_framebuffer = Framebuffer{NoCreate};
_pipelineLayout = PipelineLayout{NoCreate};
_pixels = Buffer{NoCreate};
}
const struct Quad {
Vector3 position;
Vector3 color;
} QuadData[] {
{{-0.5f, -0.5f, 0.0f}, 0xff0000_srgbf},
{{ 0.5f, -0.5f, 0.0f}, 0x00ff00_srgbf},
{{-0.5f, 0.5f, 0.0f}, 0x0000ff_srgbf},
{{ 0.5f, 0.5f, 0.0f}, 0xffffff_srgbf}
};
constexpr UnsignedShort QuadIndexData[] {
0, 1, 2, 2, 1, 3
};
void MeshVkTest::cmdDraw() {
/* This is the most simple binding (no offsets, single attribute, single
buffer) to test the basic workflow. The cmdDrawIndexed() test and others
pile on the complexity, but when everything goes wrong it's good to have
a simple test case. */
Mesh mesh{MeshLayout{MeshPrimitive::TriangleStrip}
.addBinding(0, sizeof(Vector3))
.addAttribute(0, 0, VertexFormat::Vector3, 0)
};
{
Buffer buffer{device(), BufferCreateInfo{
BufferUsage::VertexBuffer,
sizeof(Vector3)*4
}, MemoryFlag::HostVisible};
/** @todo ffs fucking casts!!! */
Utility::copy(
Containers::stridedArrayView(QuadData).slice(&Quad::position),
Containers::arrayCast<Vector3>(Containers::arrayView(buffer.dedicatedMemory().map())));
mesh.addVertexBuffer(0, std::move(buffer), 0)
.setCount(4);
}
Shader shader{device(), ShaderCreateInfo{
Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.spv"))
}};
ShaderSet shaderSet;
shaderSet
.addShader(ShaderStage::Vertex, shader, "ver"_s)
.addShader(ShaderStage::Fragment, shader, "fra"_s);
Pipeline pipeline{device(), RasterizationPipelineCreateInfo{
shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1}
.setViewport({{}, Vector2{_framebuffer.size().xy()}})
};
CommandBuffer cmd = _pool.allocate();
cmd.begin()
.beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer}
.clearColor(0, 0x1f1f1f_srgbf)
)
.bindPipeline(pipeline)
.draw(mesh)
.endRenderPass()
.copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, {
Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}}
}})
.pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, {
{Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels}
})
.end();
queue().submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait();
if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) ||
!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded))
CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found.");
CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm,
_framebuffer.size().xy(),
_pixels.dedicatedMemory().mapRead()}),
Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.tga"),
DebugTools::CompareImageToFile{_manager});
}
void MeshVkTest::cmdDrawIndexed() {
Mesh mesh{MeshLayout{MeshPrimitive::Triangles}
.addBinding(0, sizeof(Vector3))
.addAttribute(0, 0, VertexFormat::Vector3, 0)
};
{
Buffer buffer{device(), BufferCreateInfo{
BufferUsage::VertexBuffer|BufferUsage::IndexBuffer,
/* Artifical offset at the beginning to test that the offset is
used correctly in both cases */
32 + 12*4 + sizeof(QuadIndexData)
}, MemoryFlag::HostVisible};
Containers::Array<char, MemoryMapDeleter> data = buffer.dedicatedMemory().map();
/** @todo ffs fucking casts!!! */
Utility::copy(Containers::stridedArrayView(QuadData).slice(&Quad::position),
Containers::arrayCast<Vector3>(data.slice(32, 32 + 12*4)));
Utility::copy(Containers::arrayCast<const char>(QuadIndexData),
Containers::stridedArrayView(data).suffix(32 + 12*4));
mesh.addVertexBuffer(0, buffer, 32)
.setIndexBuffer(std::move(buffer), 32 + 12*4, MeshIndexType::UnsignedShort)
.setCount(6);
}
Shader shader{device(), ShaderCreateInfo{
Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.spv"))
}};
ShaderSet shaderSet;
shaderSet
.addShader(ShaderStage::Vertex, shader, "ver"_s)
.addShader(ShaderStage::Fragment, shader, "fra"_s);
Pipeline pipeline{device(), RasterizationPipelineCreateInfo{
shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1}
.setViewport({{}, Vector2{_framebuffer.size().xy()}})
};
CommandBuffer cmd = _pool.allocate();
cmd.begin()
.beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer}
.clearColor(0, 0x1f1f1f_srgbf)
)
.bindPipeline(pipeline)
.draw(mesh)
.endRenderPass()
.copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, {
Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}}
}})
.pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, {
{Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels}
})
.end();
queue().submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait();
if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) ||
!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded))
CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found.");
CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm,
_framebuffer.size().xy(),
_pixels.dedicatedMemory().mapRead()}),
Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.tga"),
DebugTools::CompareImageToFile{_manager});
}
void MeshVkTest::cmdDrawTwoAttributes() {
Mesh mesh{MeshLayout{MeshPrimitive::TriangleStrip}
.addBinding(0, sizeof(Quad))
.addAttribute(0, 0, VertexFormat::Vector3, offsetof(Quad, position))
.addAttribute(1, 0, VertexFormat::Vector3, offsetof(Quad, color))
};
{
Buffer buffer{device(), BufferCreateInfo{
BufferUsage::VertexBuffer,
sizeof(QuadData)
}, MemoryFlag::HostVisible};
/** @todo ffs fucking casts!!! */
Utility::copy(Containers::arrayCast<const char>(QuadData),
Containers::stridedArrayView(buffer.dedicatedMemory().map()));
mesh.addVertexBuffer(0, std::move(buffer), 0)
.setCount(4);
}
Shader shader{device(), ShaderCreateInfo{
Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/vertexcolor.spv"))
}};
ShaderSet shaderSet;
shaderSet
.addShader(ShaderStage::Vertex, shader, "ver"_s)
.addShader(ShaderStage::Fragment, shader, "fra"_s);
Pipeline pipeline{device(), RasterizationPipelineCreateInfo{
shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1}
.setViewport({{}, Vector2{_framebuffer.size().xy()}})
};
CommandBuffer cmd = _pool.allocate();
cmd.begin()
.beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer}
.clearColor(0, 0x1f1f1f_srgbf)
)
.bindPipeline(pipeline)
.draw(mesh)
.endRenderPass()
.copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, {
Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}}
}})
.pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, {
{Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels}
})
.end();
queue().submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait();
if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) ||
!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded))
CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found.");
CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm,
_framebuffer.size().xy(),
_pixels.dedicatedMemory().mapRead()}),
Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/vertexcolor.tga"),
/* ARM Mali (Android) has some minor off-by-one differences */
(DebugTools::CompareImageToFile{_manager, 0.5f, 0.012f}));
}
void MeshVkTest::cmdDrawTwoAttributesTwoBindings() {
Mesh mesh{MeshLayout{MeshPrimitive::TriangleStrip}
.addBinding(0, sizeof(Vector3))
.addBinding(1, sizeof(Vector3))
.addAttribute(0, 0, VertexFormat::Vector3, 0)
.addAttribute(1, 1, VertexFormat::Vector3, 0)
};
{
Buffer positions{device(), BufferCreateInfo{
BufferUsage::VertexBuffer,
sizeof(Vector3)*4
}, MemoryFlag::HostVisible};
Buffer colors{device(), BufferCreateInfo{
BufferUsage::VertexBuffer,
sizeof(Vector3)*4
}, MemoryFlag::HostVisible};
/** @todo ffs fucking casts!!! */
Utility::copy(Containers::stridedArrayView(QuadData).slice(&Quad::position),
Containers::arrayCast<Vector3>(Containers::arrayView(positions.dedicatedMemory().map())));
Utility::copy(Containers::stridedArrayView(QuadData).slice(&Quad::color),
Containers::arrayCast<Vector3>(Containers::arrayView(colors.dedicatedMemory().map())));
mesh.addVertexBuffer(0, std::move(positions), 0)
.addVertexBuffer(1, std::move(colors), 0)
.setCount(4);
}
Shader shader{device(), ShaderCreateInfo{
Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/vertexcolor.spv"))
}};
ShaderSet shaderSet;
shaderSet
.addShader(ShaderStage::Vertex, shader, "ver"_s)
.addShader(ShaderStage::Fragment, shader, "fra"_s);
Pipeline pipeline{device(), RasterizationPipelineCreateInfo{
shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1}
.setViewport({{}, Vector2{_framebuffer.size().xy()}})
};
CommandBuffer cmd = _pool.allocate();
cmd.begin()
.beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer}
.clearColor(0, 0x1f1f1f_srgbf)
)
.bindPipeline(pipeline)
.draw(mesh)
.endRenderPass()
.copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, {
Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}}
}})
.pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, {
{Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels}
})
.end();
queue().submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait();
if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) ||
!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded))
CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found.");
CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm,
_framebuffer.size().xy(),
_pixels.dedicatedMemory().mapRead()}),
Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/vertexcolor.tga"),
/* ARM Mali (Android) has some minor off-by-one differences */
(DebugTools::CompareImageToFile{_manager, 0.5f, 0.012f}));
}
void MeshVkTest::cmdDrawNullBindingRobustness2() {
if(!(_deviceRobustness2.enabledFeatures() & DeviceFeature::NullDescriptor))
CORRADE_SKIP("DeviceFeature::NullDescriptor not supported, can't test.");
Mesh mesh{MeshLayout{MeshPrimitive::TriangleStrip}
.addBinding(0, sizeof(Vector3))
.addBinding(1, sizeof(Vector3))
.addAttribute(0, 0, VertexFormat::Vector3, 0)
.addAttribute(1, 1, VertexFormat::Vector3, 0)
};
{
Buffer positions{_deviceRobustness2, BufferCreateInfo{
BufferUsage::VertexBuffer,
sizeof(Vector3)*4
}, MemoryFlag::HostVisible};
/** @todo ffs fucking casts!!! */
Utility::copy(Containers::stridedArrayView(QuadData).slice(&Quad::position),
Containers::arrayCast<Vector3>(Containers::arrayView(positions.dedicatedMemory().map())));
mesh.addVertexBuffer(0, std::move(positions), 0)
.setCount(4);
}
Shader shader{_deviceRobustness2, ShaderCreateInfo{
Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/vertexcolor.spv"))
}};
ShaderSet shaderSet;
shaderSet
.addShader(ShaderStage::Vertex, shader, "ver"_s)
.addShader(ShaderStage::Fragment, shader, "fra"_s);
Pipeline pipeline{_deviceRobustness2, RasterizationPipelineCreateInfo{
shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1}
.setViewport({{}, Vector2{_framebuffer.size().xy()}})
};
CommandBuffer cmd = _pool.allocate();
cmd.begin()
.beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer}
.clearColor(0, 0x1f1f1f_srgbf)
)
.bindPipeline(pipeline)
.draw(mesh)
.endRenderPass()
.copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, {
Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}}
}})
.pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, {
{Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels}
})
.end();
_queue.submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait();
if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) ||
!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded))
CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found.");
CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm,
_framebuffer.size().xy(),
_pixels.dedicatedMemory().mapRead()}),
Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/nullcolor.tga"),
/* ARM Mali (Android) has some minor off-by-one differences */
(DebugTools::CompareImageToFile{_manager}));
}
void MeshVkTest::cmdDrawZeroCount() {
auto&& data = CmdDrawZeroCountData[testCaseInstanceId()];
setTestCaseDescription(data.name);
Mesh mesh{MeshLayout{MeshPrimitive::Triangles}
.addBinding(0, sizeof(Vector3))
.addAttribute(0, 0, VertexFormat::Vector3, 0)
};
/* Deliberately not setting up any buffer -- the draw() should be a no-op
and thus no draw validation (and error messages) should happen */
mesh.setCount(data.count)
.setInstanceCount(data.instanceCount);
Shader shader{device(), ShaderCreateInfo{
Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.spv"))
}};
ShaderSet shaderSet;
shaderSet
.addShader(ShaderStage::Vertex, shader, "ver"_s)
.addShader(ShaderStage::Fragment, shader, "fra"_s);
Pipeline pipeline{device(), RasterizationPipelineCreateInfo{
shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1}
.setViewport({{}, Vector2{_framebuffer.size().xy()}})
};
CommandBuffer cmd = _pool.allocate();
cmd.begin()
.beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer}
.clearColor(0, 0x1f1f1f_srgbf)
)
.bindPipeline(pipeline)
.draw(mesh)
.endRenderPass()
.copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, {
Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}}
}})
.pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, {
{Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels}
})
.end();
queue().submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait();
if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) ||
!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded))
CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found.");
CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm,
_framebuffer.size().xy(),
_pixels.dedicatedMemory().mapRead()}),
Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/noop.tga"),
DebugTools::CompareImageToFile{_manager});
}
void MeshVkTest::cmdDrawNoCountSet() {
#ifdef CORRADE_NO_ASSERT
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions");
#endif
Mesh mesh{MeshLayout{MeshPrimitive::Triangles}};
Shader shader{device(), ShaderCreateInfo{
Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/noop.spv"))
}};
ShaderSet shaderSet;
shaderSet
.addShader(ShaderStage::Vertex, shader, "ver"_s)
.addShader(ShaderStage::Fragment, shader, "fra"_s);
Pipeline pipeline{device(), RasterizationPipelineCreateInfo{
shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1}
.setViewport({{}, Vector2{_framebuffer.size().xy()}})
};
CommandBuffer cmd = _pool.allocate();
cmd.begin()
.beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer}
.clearColor(0, 0x1f1f1f_srgbf)
)
.bindPipeline(pipeline);
std::ostringstream out;
Error redirectError{&out};
cmd.draw(mesh);
CORRADE_COMPARE(out.str(), "Vk::CommandBuffer::draw(): Mesh::setCount() was never called, probably a mistake?\n");
}
void MeshVkTest::cmdDrawDynamicPrimitive() {
if(!(_deviceExtendedDynamicState.enabledFeatures() & DeviceFeature::ExtendedDynamicState))
CORRADE_SKIP("DeviceFeature::ExtendedDynamicState not supported, can't test.");
if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) ||
!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded))
CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found.");
Mesh mesh{MeshLayout{MeshPrimitive::TriangleStrip}
.addBinding(0, sizeof(Vector3))
.addAttribute(0, 0, VertexFormat::Vector3, 0)
};
{
Buffer buffer{_deviceExtendedDynamicState, BufferCreateInfo{
BufferUsage::VertexBuffer,
sizeof(Vector3)*4
}, MemoryFlag::HostVisible};
/** @todo ffs fucking casts!!! */
Utility::copy(
Containers::stridedArrayView(QuadData).slice(&Quad::position),
Containers::arrayCast<Vector3>(Containers::arrayView(buffer.dedicatedMemory().map())));
mesh.addVertexBuffer(0, std::move(buffer), 0)
.setCount(4);
}
Shader shader{_deviceExtendedDynamicState, ShaderCreateInfo{
Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.spv"))
}};
ShaderSet shaderSet;
shaderSet
.addShader(ShaderStage::Vertex, shader, "ver"_s)
.addShader(ShaderStage::Fragment, shader, "fra"_s);
/* Create the pipeline with Triangles while the mesh is TriangleStrip */
MeshLayout pipelineLayout{MeshPrimitive::Triangles};
pipelineLayout
.addBinding(0, sizeof(Vector3))
.addAttribute(0, 0, VertexFormat::Vector3, 0);
Pipeline pipeline{_deviceExtendedDynamicState, RasterizationPipelineCreateInfo{
shaderSet, pipelineLayout, _pipelineLayout, _renderPass, 0, 1}
.setViewport({{}, Vector2{_framebuffer.size().xy()}})
.setDynamicStates(DynamicRasterizationState::MeshPrimitive)
};
CommandBuffer cmd = _pool.allocate();
cmd.begin()
.beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer}
.clearColor(0, 0x1f1f1f_srgbf)
)
.bindPipeline(pipeline)
.draw(mesh)
.endRenderPass()
.copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, {
Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}}
}})
.pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, {
{Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels}
})
.end();
_queue.submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait();
if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) ||
!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded))
CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found.");
CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm,
_framebuffer.size().xy(),
_pixels.dedicatedMemory().mapRead()}),
Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.tga"),
DebugTools::CompareImageToFile{_manager});
}
void MeshVkTest::cmdDrawDynamicStride() {
if(!(_deviceExtendedDynamicState.enabledFeatures() & DeviceFeature::ExtendedDynamicState))
CORRADE_SKIP("DeviceFeature::ExtendedDynamicState not supported, can't test.");
if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) ||
!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded))
CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found.");
Mesh mesh{MeshLayout{MeshPrimitive::TriangleStrip}
.addBinding(0, sizeof(Vector3))
.addAttribute(0, 0, VertexFormat::Vector3, 0)
};
{
Buffer buffer{_deviceExtendedDynamicState, BufferCreateInfo{
BufferUsage::VertexBuffer,
sizeof(Vector3)*4
}, MemoryFlag::HostVisible};
/** @todo ffs fucking casts!!! */
Utility::copy(
Containers::stridedArrayView(QuadData).slice(&Quad::position),
Containers::arrayCast<Vector3>(Containers::arrayView(buffer.dedicatedMemory().map())));
mesh.addVertexBuffer(0, std::move(buffer), 0)
.setCount(4);
}
Shader shader{_deviceExtendedDynamicState, ShaderCreateInfo{
Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.spv"))
}};
ShaderSet shaderSet;
shaderSet
.addShader(ShaderStage::Vertex, shader, "ver"_s)
.addShader(ShaderStage::Fragment, shader, "fra"_s);
/* Create the pipeline with a 1 kB stride, while the actual stride is
different */
MeshLayout pipelineLayout{MeshPrimitive::TriangleStrip};
pipelineLayout
.addBinding(0, 1024)
.addAttribute(0, 0, VertexFormat::Vector3, 0);
Pipeline pipeline{_deviceExtendedDynamicState, RasterizationPipelineCreateInfo{
shaderSet, pipelineLayout, _pipelineLayout, _renderPass, 0, 1}
.setViewport({{}, Vector2{_framebuffer.size().xy()}})
.setDynamicStates(DynamicRasterizationState::VertexInputBindingStride)
};
CommandBuffer cmd = _pool.allocate();
cmd.begin()
.beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer}
.clearColor(0, 0x1f1f1f_srgbf)
)
.bindPipeline(pipeline)
.draw(mesh)
.endRenderPass()
.copyImageToBuffer({_color, Vk::ImageLayout::TransferSource, _pixels, {
Vk::BufferImageCopy2D{0, Vk::ImageAspect::Color, 0, {{}, _framebuffer.size().xy()}}
}})
.pipelineBarrier(Vk::PipelineStage::Transfer, Vk::PipelineStage::Host, {
{Vk::Access::TransferWrite, Vk::Access::HostRead, _pixels}
})
.end();
_queue.submit({SubmitInfo{}.setCommandBuffers({cmd})}).wait();
if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) ||
!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded))
CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found.");
CORRADE_COMPARE_WITH((ImageView2D{Magnum::PixelFormat::RGBA8Unorm,
_framebuffer.size().xy(),
_pixels.dedicatedMemory().mapRead()}),
Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.tga"),
DebugTools::CompareImageToFile{_manager});
}
void MeshVkTest::cmdDrawDynamicStrideInsufficientImplementation() {
#ifdef CORRADE_NO_ASSERT
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions");
#endif
if(device().isExtensionEnabled<Extensions::EXT::extended_dynamic_state>())
CORRADE_SKIP("VK_EXT_extended_dynamic_state enabled, can't test.");
Mesh mesh{MeshLayout{MeshPrimitive::TriangleStrip}
.addBinding(0, sizeof(Vector3))
.addAttribute(0, 0, VertexFormat::Vector3, 0)
};
{
Buffer buffer{device(), BufferCreateInfo{
BufferUsage::VertexBuffer,
sizeof(Vector3)*4
}, MemoryFlag::HostVisible};
/** @todo ffs fucking casts!!! */
Utility::copy(
Containers::stridedArrayView(QuadData).slice(&Quad::position),
Containers::arrayCast<Vector3>(Containers::arrayView(buffer.dedicatedMemory().map())));
mesh.addVertexBuffer(0, std::move(buffer), 0)
.setCount(4);
}
Shader shader{device(), ShaderCreateInfo{
Utility::Directory::read(Utility::Directory::join(VK_TEST_DIR, "MeshTestFiles/flat.spv"))
}};
ShaderSet shaderSet;
shaderSet
.addShader(ShaderStage::Vertex, shader, "ver"_s)
.addShader(ShaderStage::Fragment, shader, "fra"_s);
/* Create a pipeline without any dynamic state and then wrap it with fake
enabled vertex input binding stride -- doing so directly would trigger
validation layer failures (using dynamic state from a non-enabled ext),
which we don't want */
Pipeline pipeline{device(), RasterizationPipelineCreateInfo{
shaderSet, mesh.layout(), _pipelineLayout, _renderPass, 0, 1}
.setViewport({{}, Vector2{_framebuffer.size().xy()}})
};
Pipeline fakeDynamicStatePipeline = Pipeline::wrap(device(),
PipelineBindPoint::Rasterization, pipeline,
DynamicRasterizationState::VertexInputBindingStride);
CommandBuffer cmd = _pool.allocate();
cmd.begin()
.beginRenderPass(Vk::RenderPassBeginInfo{_renderPass, _framebuffer}
.clearColor(0, 0x1f1f1f_srgbf)
)
.bindPipeline(fakeDynamicStatePipeline);
std::ostringstream out;
Error redirectError{&out};
cmd.draw(mesh);
CORRADE_COMPARE(out.str(), "Vk::CommandBuffer::draw(): dynamic strides supplied for an implementation without extended dynamic state\n");
}
}}}}
CORRADE_TEST_MAIN(Magnum::Vk::Test::MeshVkTest)

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save