Add new sample for buffer device address (#1144)

* Started work on buffer device address sample

* Code cleanup

* Added BDA sample to readm

* Added android build files for BDA sample

* Replaces all uniform buffers with references
Comments and code cleanup
This commit is contained in:
Sascha Willems 2024-07-03 22:04:57 +02:00 committed by GitHub
parent c598b1e7ab
commit 8b4ee59033
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 580 additions and 0 deletions

View file

@ -474,6 +474,10 @@ Basic sample showing how to use shader objects that can be used to replace pipel
Shows how to do host image copies, which heavily simplify the host to device image process by fully skipping the staging process. Shows how to do host image copies, which heavily simplify the host to device image process by fully skipping the staging process.
#### [Buffer device address (VK_KHR_buffer_device_addres)](./examples/bufferdeviceaddress/)<br/>
Demonstrates the use of virtual GPU addresses to directly access buffer data in shader. Instead of e.g. using descriptors to access uniforms, with this extension you simply provide an address to the memory you want to read from in the shader and that address can be arbitrarily changed e.g. via a push constant.
### Misc ### Misc
#### [Vulkan Gears](examples/gears/) #### [Vulkan Gears](examples/gears/)

View file

@ -0,0 +1,35 @@
cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
set(NAME bufferdeviceaddress)
set(SRC_DIR ../../../examples/${NAME})
set(BASE_DIR ../../../base)
set(EXTERNAL_DIR ../../../external)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
add_library(native-lib SHARED ${EXAMPLE_SRC})
add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
include_directories(${BASE_DIR})
include_directories(${EXTERNAL_DIR})
include_directories(${EXTERNAL_DIR}/glm)
include_directories(${EXTERNAL_DIR}/imgui)
include_directories(${EXTERNAL_DIR}/tinygltf)
include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
target_link_libraries(
native-lib
native-app-glue
libbase
android
log
z
)

View file

@ -0,0 +1,71 @@
apply plugin: 'com.android.application'
apply from: '../gradle/outputfilename.gradle'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "de.saschawillems.vulkanBufferDeviceAddress"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
ndk {
abiFilters rootProject.ext.abiFilters
}
externalNativeBuild {
cmake {
cppFlags "-std=c++14"
arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
}
}
}
sourceSets {
main.assets.srcDirs = ['assets']
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
task copyTask {
copy {
from '../../common/res/drawable'
into "src/main/res/drawable"
include 'icon.png'
}
copy {
from rootProject.ext.shaderPath + 'glsl/base'
into 'assets/shaders/glsl/base'
include '*.spv'
}
copy {
from rootProject.ext.shaderPath + 'glsl/bufferdeviceaddress'
into 'assets/shaders/glsl/bufferdeviceaddress'
include '*.*'
}
copy {
from rootProject.ext.assetPath + 'models'
into 'assets/models'
include 'cube.gltf'
}
copy {
from rootProject.ext.assetPath + 'textures'
into 'assets/textures'
include 'crate01_color_height_rgba.ktx'
}
}
preBuild.dependsOn copyTask

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.saschawillems.vulkanBufferDeviceAddress">
<application
android:label="Vulkan buffer device address"
android:icon="@drawable/icon"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
<activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
android:screenOrientation="landscape"
android:configChanges="orientation|keyboardHidden"
android:exported="true">
<meta-data android:name="android.app.lib_name"
android:value="native-lib" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-feature android:name="android.hardware.gamepad" android:required="false" />
</manifest>

View file

@ -0,0 +1,58 @@
/*
* Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
*
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
*/
package de.saschawillems.vulkanSample;
import android.app.AlertDialog;
import android.app.NativeActivity;
import android.content.DialogInterface;
import android.content.pm.ApplicationInfo;
import android.os.Bundle;
import java.util.concurrent.Semaphore;
public class VulkanActivity extends NativeActivity {
static {
// Load native library
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
// Use a semaphore to create a modal dialog
private final Semaphore semaphore = new Semaphore(0, true);
public void showAlert(final String message)
{
final VulkanActivity activity = this;
ApplicationInfo applicationInfo = activity.getApplicationInfo();
final String applicationName = applicationInfo.nonLocalizedLabel.toString();
this.runOnUiThread(new Runnable() {
public void run() {
AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
builder.setTitle(applicationName);
builder.setMessage(message);
builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
semaphore.release();
}
});
builder.setCancelable(false);
AlertDialog dialog = builder.create();
dialog.show();
}
});
try {
semaphore.acquire();
}
catch (InterruptedException e) { }
}
}

View file

@ -85,6 +85,7 @@ endfunction(buildExamples)
set(EXAMPLES set(EXAMPLES
bloom bloom
bufferdeviceaddress
computecloth computecloth
computecullandlod computecullandlod
computeheadless computeheadless

View file

@ -0,0 +1,324 @@
/*
* Vulkan Example - Buffer device address
*
* This sample shows how to read data from a buffer device address (aka "reference") instead of using uniforms
* The application passes buffer device addresses to the shader via push constants, and the shader then simply reads the data behind that address
* See cube.vert for the shader side of things
*
* Copyright (C) 2024 by Sascha Willems - www.saschawillems.de
*
*/
#include "vulkanexamplebase.h"
#include "VulkanglTFModel.h"
class VulkanExample : public VulkanExampleBase
{
public:
bool animate = true;
struct Cube {
glm::mat4 modelMatrix;
vks::Buffer buffer;
glm::vec3 rotation;
VkDeviceAddress bufferDeviceAddress{};
};
std::array<Cube, 2> cubes{};
vks::Texture2D texture;
vkglTF::Model model;
// Global matrices
struct Scene {
glm::mat4 mvp;
vks::Buffer buffer;
VkDeviceAddress bufferDeviceAddress{};
} scene;
VkPipeline pipeline{ VK_NULL_HANDLE };
VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE };
VkDescriptorSet descriptorSet{ VK_NULL_HANDLE };
VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE };
PFN_vkGetBufferDeviceAddressKHR vkGetBufferDeviceAddressKHR{ VK_NULL_HANDLE };
VkPhysicalDeviceBufferDeviceAddressFeatures enabledBufferDeviceAddresFeatures{};
// This sample passes the buffer references ("pointer") using push constants, the shader then reads data from that buffer address
struct PushConstantBlock {
// Reference to the global matrices
VkDeviceAddress sceneReference;
// Reference to the per model matrices
VkDeviceAddress modelReference;
};
VulkanExample() : VulkanExampleBase()
{
title = "Buffer device address";
camera.type = Camera::CameraType::lookat;
camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f);
camera.setRotation(glm::vec3(0.0f, 0.0f, 0.0f));
camera.setTranslation(glm::vec3(0.0f, 0.0f, -5.0f));
enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
enabledInstanceExtensions.push_back(VK_KHR_DEVICE_GROUP_CREATION_EXTENSION_NAME);
enabledDeviceExtensions.push_back(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
enabledDeviceExtensions.push_back(VK_KHR_DEVICE_GROUP_EXTENSION_NAME);
enabledBufferDeviceAddresFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES;
enabledBufferDeviceAddresFeatures.bufferDeviceAddress = VK_TRUE;
deviceCreatepNextChain = &enabledBufferDeviceAddresFeatures;
}
~VulkanExample()
{
if (device) {
vkDestroyPipeline(device, pipeline, nullptr);
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
texture.destroy();
for (auto cube : cubes) {
cube.buffer.destroy();
}
scene.buffer.destroy();
}
}
virtual void getEnabledFeatures()
{
if (deviceFeatures.samplerAnisotropy) {
enabledFeatures.samplerAnisotropy = VK_TRUE;
};
}
void loadAssets()
{
const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
model.loadFromFile(getAssetPath() + "models/cube.gltf", vulkanDevice, queue, glTFLoadingFlags);
texture.loadFromFile(getAssetPath() + "textures/crate01_color_height_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
}
// We pass all data via buffer device addresses, so we only allocate descriptors for the images
void setupDescriptors()
{
// Pool
std::vector<VkDescriptorPoolSize> descriptorPoolSizes = {
vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1)
};
VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(descriptorPoolSizes, 2);
VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
// Layout
std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0)
};
VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
// Set
VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &texture.descriptor)
};
vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
}
void preparePipelines()
{
// The buffer addresses will be passed to the shader using push constants
// That way it's very easy to do a draw call, change the reference to another buffer (or part of that buffer) and do the next draw call using different data
VkPushConstantRange pushConstantRange{};
pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
pushConstantRange.offset = 0;
pushConstantRange.size = sizeof(PushConstantBlock);
VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo();
pipelineLayoutCI.pushConstantRangeCount = 1;
pipelineLayoutCI.pPushConstantRanges = &pushConstantRange;
pipelineLayoutCI.setLayoutCount = 1;
pipelineLayoutCI.pSetLayouts = &descriptorSetLayout;
VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout));
const std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast<uint32_t>(dynamicStateEnables.size()), 0);
std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages = {
loadShader(getShadersPath() + "bufferdeviceaddress/cube.vert.spv", VK_SHADER_STAGE_VERTEX_BIT),
loadShader(getShadersPath() + "bufferdeviceaddress/cube.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT)
};
VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
pipelineCI.pInputAssemblyState = &inputAssemblyStateCI;
pipelineCI.pRasterizationState = &rasterizationStateCI;
pipelineCI.pColorBlendState = &colorBlendStateCI;
pipelineCI.pMultisampleState = &multisampleStateCI;
pipelineCI.pViewportState = &viewportStateCI;
pipelineCI.pDepthStencilState = &depthStencilStateCI;
pipelineCI.pDynamicState = &dynamicStateCI;
pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
pipelineCI.pStages = shaderStages.data();
pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Color });
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
}
void prepareBuffers()
{
// Note that we don't use this buffer for uniforms but rather pass it's address as a reference to the shader, so isntead of the uniform buffer usage we use a different flag
VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &scene.buffer, sizeof(glm::mat4)));
VK_CHECK_RESULT(scene.buffer.map());
// Get the device of this buffer that is later on passed to the shader (aka "reference")
VkBufferDeviceAddressInfo bufferDeviceAdressInfo{};
bufferDeviceAdressInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO;
bufferDeviceAdressInfo.buffer = scene.buffer.buffer;
scene.bufferDeviceAddress = vkGetBufferDeviceAddressKHR(device, &bufferDeviceAdressInfo);
for (auto& cube : cubes) {
// Note that we don't use this buffer for uniforms but rather pass it's address as a reference to the shader, so isntead of the uniform buffer usage we use a different flag
VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &cube.buffer, sizeof(glm::mat4)));
VK_CHECK_RESULT(cube.buffer.map());
// Get the device of this buffer that is later on passed to the shader (aka "reference")
bufferDeviceAdressInfo.buffer = cube.buffer.buffer;
cube.bufferDeviceAddress = vkGetBufferDeviceAddressKHR(device, &bufferDeviceAdressInfo);
}
updateBuffers();
}
void updateBuffers()
{
scene.mvp = camera.matrices.perspective * camera.matrices.view;
memcpy(scene.buffer.mapped, &scene, sizeof(glm::mat4));
cubes[0].modelMatrix = glm::translate(glm::mat4(1.0f), glm::vec3(-2.0f, 0.0f, 0.0f));
cubes[1].modelMatrix = glm::translate(glm::mat4(1.0f), glm::vec3(1.5f, 0.5f, 0.0f));
for (auto& cube : cubes) {
cube.modelMatrix = glm::rotate(cube.modelMatrix, glm::radians(cube.rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
cube.modelMatrix = glm::rotate(cube.modelMatrix, glm::radians(cube.rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
cube.modelMatrix = glm::rotate(cube.modelMatrix, glm::radians(cube.rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
cube.modelMatrix = glm::scale(cube.modelMatrix, glm::vec3(0.25f));
memcpy(cube.buffer.mapped, &cube.modelMatrix, sizeof(glm::mat4));
}
}
void prepare()
{
VulkanExampleBase::prepare();
// We need this extension function to get the address of a buffer so we can pass it to the shader
vkGetBufferDeviceAddressKHR = reinterpret_cast<PFN_vkGetBufferDeviceAddressKHR>(vkGetDeviceProcAddr(device, "vkGetBufferDeviceAddressKHR"));
loadAssets();
prepareBuffers();
setupDescriptors();
preparePipelines();
buildCommandBuffers();
prepared = true;
}
void buildCommandBuffers()
{
VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
VkClearValue clearValues[2];
clearValues[0].color = defaultClearColor;
clearValues[1].depthStencil = { 1.0f, 0 };
VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
renderPassBeginInfo.renderPass = renderPass;
renderPassBeginInfo.renderArea.offset.x = 0;
renderPassBeginInfo.renderArea.offset.y = 0;
renderPassBeginInfo.renderArea.extent.width = width;
renderPassBeginInfo.renderArea.extent.height = height;
renderPassBeginInfo.clearValueCount = 2;
renderPassBeginInfo.pClearValues = clearValues;
for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) {
renderPassBeginInfo.framebuffer = frameBuffers[i];
VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
model.bindBuffers(drawCmdBuffers[i]);
// Instead of using descriptors to pass global and per-model matrices to the shader, we can now simply pass buffer references via push constants
// The reader then simply reads data from the address of that reference
PushConstantBlock references{};
// Pass pointer to the global matrix via a buffer device address
references.sceneReference = scene.bufferDeviceAddress;
for (auto& cube : cubes) {
// Pass pointer to this cube's data buffer via a buffer device address
// So instead of having to bind different descriptors, we only pass a different device address
// This doesn't have to be an address from a different buffer, but could very well be just another address in the same buffer
references.modelReference = cube.bufferDeviceAddress;
vkCmdPushConstants(drawCmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PushConstantBlock), &references);
model.draw(drawCmdBuffers[i]);
}
drawUI(drawCmdBuffers[i]);
vkCmdEndRenderPass(drawCmdBuffers[i]);
VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
}
}
void draw()
{
VulkanExampleBase::prepareFrame();
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
VulkanExampleBase::submitFrame();
}
virtual void render()
{
if (!prepared)
return;
draw();
if (animate && !paused) {
cubes[0].rotation.x += 2.5f * frameTimer;
if (cubes[0].rotation.x > 360.0f)
cubes[0].rotation.x -= 360.0f;
cubes[1].rotation.y += 2.0f * frameTimer;
if (cubes[1].rotation.x > 360.0f)
cubes[1].rotation.x -= 360.0f;
}
if ((camera.updated) || (animate && !paused)) {
updateBuffers();
}
}
virtual void OnUpdateUIOverlay(vks::UIOverlay* overlay)
{
if (overlay->header("Settings")) {
overlay->checkBox("Animate", &animate);
}
}
};
VULKAN_EXAMPLE_MAIN()

View file

@ -0,0 +1,20 @@
/* Copyright (c) 2024, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
#version 450
layout (set = 0, binding = 0) uniform sampler2D samplerColorMap;
layout (location = 0) in vec3 inNormal;
layout (location = 1) in vec3 inColor;
layout (location = 2) in vec2 inUV;
layout (location = 0) out vec4 outFragColor;
void main()
{
outFragColor = texture(samplerColorMap, inUV) * vec4(inColor, 1.0);
}

Binary file not shown.

View file

@ -0,0 +1,42 @@
/* Copyright (c) 2024, Sascha Willems
*
* SPDX-License-Identifier: MIT
*
*/
#version 450
#extension GL_EXT_scalar_block_layout: require
#extension GL_EXT_buffer_reference : require
layout (location = 0) in vec3 inPos;
layout (location = 1) in vec3 inNormal;
layout (location = 2) in vec2 inUV;
layout (location = 3) in vec3 inColor;
layout (buffer_reference, scalar) readonly buffer MatrixReference {
mat4 matrix;
};
layout (push_constant) uniform PushConstants
{
// Pointer to the buffer with the scene's MVP matrix
MatrixReference sceneDataReference;
// Pointer to the buffer for the data for each model
MatrixReference modelDataReference;
} pushConstants;
layout (location = 0) out vec3 outNormal;
layout (location = 1) out vec3 outColor;
layout (location = 2) out vec2 outUV;
void main()
{
MatrixReference sceneData = pushConstants.sceneDataReference;
MatrixReference modelData = pushConstants.modelDataReference;
outNormal = inNormal;
outColor = inColor;
outUV = inUV;
gl_Position = sceneData.matrix * modelData.matrix * vec4(inPos.xyz, 1.0);
}

Binary file not shown.