init
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
# Copyright (c) 2021-2023, Arm Limited and Contributors
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 the "License";
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
# The OpenCL interoperability only works on Android
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
if(ANDROID)
|
||||
add_sample_with_tags(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Arm"
|
||||
NAME "Arm OpenCL Interoperability"
|
||||
DESCRIPTION "Example showing sharing resources between OpenCL and Vulkan on Arm devices"
|
||||
TAGS "arm"
|
||||
LIBS opencl
|
||||
FILES
|
||||
../open_cl_common/open_cl_functions.inl
|
||||
../open_cl_common/open_cl_utils.h
|
||||
../open_cl_common/open_cl_utils.cpp)
|
||||
endif()
|
||||
@@ -0,0 +1,182 @@
|
||||
////
|
||||
- Copyright (c) 2021-2023, Arm Limited and Contributors
|
||||
-
|
||||
- SPDX-License-Identifier: Apache-2.0
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 the "License";
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-
|
||||
////
|
||||
= OpenCL interoperability
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/open_cl_interop_arm[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
== Overview
|
||||
|
||||
In certain scenarios OpenCL is used for compute, while another API is used for graphics, and interoperability between two APIs becomes important in this case.
|
||||
For example, in AR applications graphics rendering is often combined with machine learning workloads, which can be executed using OpenCL.
|
||||
In such cases we need zero-copy data sharing and efficient workload synchronization to achieve the best performance.
|
||||
|
||||
While there are no dedicated extensions for direct Vulkan - OpenCL interoperability at the moment, both APIs provide generic sharing mechanism that can be used to achieve it.
|
||||
This sample demonstrates an approach for zero-copy data sharing using Android Hardware Buffers and corresponding extensions for Vulkan and OpenCL.
|
||||
|
||||
== Data sharing
|
||||
|
||||
Zero-copy assumes that both APIs use the same region of memory for an image or a buffer.
|
||||
In case of OpenCL and Vulkan we can use extensions to share data using:
|
||||
|
||||
* Android Hardware Buffers
|
||||
* dma_buf
|
||||
* Host memory
|
||||
|
||||
This sample covers one of these options, which is more relevant to mobile developers working with Vulkan: Android Hardware Buffers.
|
||||
|
||||
== Android Hardware Buffers
|
||||
|
||||
Native hardware buffers on Android represent a region of memory which can be bound to Vulkan, OpenGL ES or OpenCL primitives.
|
||||
This allows us to reuse it with two different APIs:
|
||||
|
||||
image::./images/shared_hardware_buffer.png[Sample]
|
||||
|
||||
=== Support by Vulkan API
|
||||
|
||||
In Vulkan hardware buffers can be imported or exported using https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_ANDROID_external_memory_android_hardware_buffer.html[VK_ANDROID_external_memory_android_hardware_buffer] extension.
|
||||
AHardwareBuffer can be bound to a `VkDeviceMemory` object, which serves as an allocation for objects of type `VkImage` or `VkBuffer`.
|
||||
|
||||
If we need to use an image backed by an AHardwareBuffer, we must specify while creating the image.
|
||||
It is done by assigning a pointer to the following structure to `pNext` field of `VkImageCreateInfo`:
|
||||
|
||||
----
|
||||
VkExternalMemoryImageCreateInfo external_memory_image_create_info;
|
||||
external_memory_image_create_info.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO;
|
||||
external_memory_image_create_info.pNext = nullptr;
|
||||
external_memory_image_create_info.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID;
|
||||
----
|
||||
|
||||
This image (or buffer) must be specified as target during memory allocation.
|
||||
Below you can see, how the image object which is supposed to be shared with OpenCL is assigned to the `image` field of `VkMemoryDedicatedAllocateInfo`, while `buffer` field is assigned with `VK_NULL_HANDLE`:
|
||||
|
||||
----
|
||||
VkMemoryDedicatedAllocateInfo dedicated_allocate_info;
|
||||
dedicated_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;
|
||||
dedicated_allocate_info.pNext = nullptr;
|
||||
dedicated_allocate_info.buffer = VK_NULL_HANDLE;
|
||||
dedicated_allocate_info.image = shared_image;
|
||||
----
|
||||
|
||||
==== Export from Vulkan API
|
||||
|
||||
A pointer to `dedicated_allocate_info` is provided as `pNext` of `VkExportMemoryAllocateInfo`.
|
||||
|
||||
----
|
||||
VkExportMemoryAllocateInfo export_memory_allocate_Info;
|
||||
export_memory_allocate_Info.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO;
|
||||
export_memory_allocate_Info.pNext = &dedicated_allocate_info;
|
||||
export_memory_allocate_Info.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID;
|
||||
----
|
||||
|
||||
This is specified as `pNext` of `VkMemoryAllocateInfo`, while `allocationSize` is set to 0.
|
||||
You can find more info on this in the https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#VkMemoryRequirements[specification].
|
||||
|
||||
Once the memory is allocated *and* bound to the image (or buffer), we can export a handle of type `AHardwareBuffer`:
|
||||
|
||||
----
|
||||
VkMemoryGetAndroidHardwareBufferInfoANDROID info;
|
||||
info.sType = VK_STRUCTURE_TYPE_MEMORY_GET_ANDROID_HARDWARE_BUFFER_INFO_ANDROID;
|
||||
info.pNext = nullptr;
|
||||
info.memory = shared_memory;
|
||||
vkGetMemoryAndroidHardwareBufferANDROID(device, &info, &hardware_buffer);
|
||||
----
|
||||
|
||||
==== Import to Vulkan API
|
||||
|
||||
In this case AHardwareBuffer is created first and then used for `VkDeviceMemory` allocation.
|
||||
|
||||
NOTE: there are certain restrictions on image or buffer format and usage.
|
||||
You can find the list of Android Hardware Buffer formats and their Vulkan counterparts in this https://developer.android.com/ndk/reference/group/a-hardware-buffer#ahardwarebuffer_format[table].
|
||||
|
||||
A pointer to `dedicated_allocate_info` is provided as `pNext` of `VkImportAndroidHardwareBufferInfoANDROID`.
|
||||
Also the hardware buffer we want to import must be provided during this stage:
|
||||
|
||||
----
|
||||
VkImportAndroidHardwareBufferInfoANDROID import_info;
|
||||
import_info.sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID;
|
||||
import_info.pNext = &dedicated_allocate_info;
|
||||
import_info.buffer = hardware_buffer;
|
||||
----
|
||||
|
||||
We need to specify this data as `pNext` in `VkMemoryAllocateInfo`.
|
||||
In case of import, `allocationSize` and `memoryTypeIndex` should be based on the results of `vkGetAndroidHardwareBufferPropertiesANDROID`:
|
||||
|
||||
----
|
||||
VkAndroidHardwareBufferPropertiesANDROID buffer_properties;
|
||||
buffer_properties.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID;
|
||||
buffer_properties.pNext = nullptr;
|
||||
vkGetAndroidHardwareBufferPropertiesANDROID(device, hardware_buffer, &buffer_properties);
|
||||
----
|
||||
|
||||
Then the memory is allocated and bound to the image as usual.
|
||||
|
||||
=== Support by OpenCL
|
||||
|
||||
On Arm devices Android hardware buffers (as well as dma_buf and host memory) can be imported to OpenCL using https://www.khronos.org/registry/OpenCL/extensions/arm/cl_arm_import_memory.txt[cl_arm_import_memory] extension.
|
||||
|
||||
`cl_mem` object is retrieved in the following way:
|
||||
|
||||
----
|
||||
const cl_import_properties_arm import_properties[3] = {
|
||||
CL_IMPORT_TYPE_ARM, CL_IMPORT_TYPE_ANDROID_HARDWARE_BUFFER_ARM,
|
||||
0};
|
||||
|
||||
cl_int result = CL_SUCCESS;
|
||||
cl_mem shared_cl_mem = clImportMemoryARM(context,
|
||||
CL_MEM_READ_WRITE,
|
||||
import_properties,
|
||||
hardware_buffer,
|
||||
CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM,
|
||||
&result);
|
||||
----
|
||||
|
||||
In `cl_import_properties_arm` array the types and values of the properties are listed one by one with a 0 as a terminator in the end.
|
||||
|
||||
We need to specify the type of the imported object in this list (in our case it's `CL_IMPORT_TYPE_ANDROID_HARDWARE_BUFFER_ARM`).
|
||||
|
||||
== The sample
|
||||
|
||||
In this sample a simple OpenCL kernel is executed in the beginning of each frame to fill a `cl_mem` object backed by an `AHardwareBuffer` with a simple pattern (this pattern changes over time).
|
||||
This `AHardwareBuffer` is exported from a `VkDeviceMemory` object which is bound to a texture.
|
||||
After the OpenCL kernel is executed, the filled texture is displayed.
|
||||
|
||||
The texture is displayed as a quad using the approach and shaders from link:../../api/texture_loading[Texture Loading] sample.
|
||||
|
||||
The sample uses only one shared texture for simplicity, but in real applications it's worth having as many shared textures as there are framebuffers.
|
||||
This way the OpenCL kernel would be executed for the current frame, while the previous frame is being rendered and displayed (double buffering).
|
||||
|
||||
Synchronization between APIs is performed on host:
|
||||
|
||||
* OpenCL queue is flushed before rendering
|
||||
* A `VkFence` object is used to make sure the rendering is finished
|
||||
|
||||
image::./images/sample.png[Sample]
|
||||
|
||||
== Conclusion
|
||||
|
||||
Both OpenCL and Vulkan extensions allow us to import or export certain handle types, which represent a region of memory.
|
||||
These memory regions can be used to achieve zero-copy data sharing.
|
||||
In this sample one of such handle types is demonstrated: Android Hardware Buffers.
|
||||
|
||||
The extensions used in this case are:
|
||||
|
||||
* https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_ANDROID_external_memory_android_hardware_buffer.html[VK_ANDROID_external_memory_android_hardware_buffer]
|
||||
* https://www.khronos.org/registry/OpenCL/extensions/arm/cl_arm_import_memory.txt[cl_arm_import_memory]
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 595 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
@@ -0,0 +1,660 @@
|
||||
/* Copyright (c) 2021-2025, Arm Limited and Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 the "License";
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "open_cl_interop_arm.h"
|
||||
|
||||
#include "common/vk_common.h"
|
||||
#include "filesystem/legacy.h"
|
||||
#include "gui.h"
|
||||
|
||||
#define CL_FUNCTION_DEFINITIONS
|
||||
#include "../open_cl_common/open_cl_utils.h"
|
||||
#include <strstream>
|
||||
|
||||
#ifdef VK_USE_PLATFORM_ANDROID_KHR
|
||||
# include <android/hardware_buffer.h>
|
||||
# include <android/hardware_buffer_jni.h>
|
||||
#endif
|
||||
|
||||
struct CLData
|
||||
{
|
||||
cl_context context{nullptr};
|
||||
cl_device_id device_id{nullptr};
|
||||
cl_command_queue command_queue{nullptr};
|
||||
cl_program program{nullptr};
|
||||
cl_kernel kernel{nullptr};
|
||||
cl_mem image{nullptr};
|
||||
};
|
||||
|
||||
OpenCLInteropArm::OpenCLInteropArm()
|
||||
{
|
||||
zoom = -3.5f;
|
||||
title = "Interoperability with OpenCL";
|
||||
|
||||
add_device_extension(VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME);
|
||||
|
||||
add_device_extension(VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME);
|
||||
add_device_extension(VK_KHR_MAINTENANCE1_EXTENSION_NAME);
|
||||
add_device_extension(VK_KHR_BIND_MEMORY_2_EXTENSION_NAME);
|
||||
add_device_extension(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME);
|
||||
add_instance_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
|
||||
add_instance_extension(VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME);
|
||||
add_device_extension(VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME);
|
||||
add_device_extension(VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME);
|
||||
add_device_extension(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
OpenCLInteropArm::~OpenCLInteropArm()
|
||||
{
|
||||
get_device().wait_idle();
|
||||
|
||||
vkDestroyPipeline(get_device().get_handle(), pipeline, nullptr);
|
||||
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layout, nullptr);
|
||||
vkDestroyFence(get_device().get_handle(), rendering_finished_fence, nullptr);
|
||||
vkDestroySampler(get_device().get_handle(), shared_texture.sampler, nullptr);
|
||||
vkDestroyImageView(get_device().get_handle(), shared_texture.view, nullptr);
|
||||
vkDestroyImage(get_device().get_handle(), shared_texture.image, nullptr);
|
||||
vkFreeMemory(get_device().get_handle(), shared_texture.memory, nullptr);
|
||||
|
||||
if (cl_data)
|
||||
{
|
||||
clReleaseMemObject(cl_data->image);
|
||||
clReleaseContext(cl_data->context);
|
||||
delete cl_data;
|
||||
}
|
||||
|
||||
unload_opencl();
|
||||
}
|
||||
|
||||
bool OpenCLInteropArm::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
if (!ApiVulkanSample::prepare(options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
cl_data = new CLData{};
|
||||
|
||||
prepare_open_cl_resources();
|
||||
prepare_shared_resources();
|
||||
generate_quad();
|
||||
prepare_uniform_buffers();
|
||||
setup_descriptor_set_layout();
|
||||
prepare_pipelines();
|
||||
setup_descriptor_pool();
|
||||
setup_descriptor_set();
|
||||
build_command_buffers();
|
||||
|
||||
auto fence_create_info = vkb::initializers::fence_create_info(VK_FENCE_CREATE_SIGNALED_BIT);
|
||||
vkCreateFence(get_device().get_handle(), &fence_create_info, nullptr, &rendering_finished_fence);
|
||||
|
||||
prepared = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenCLInteropArm::render(float delta_time)
|
||||
{
|
||||
if (!prepared)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
total_time_passed += delta_time;
|
||||
|
||||
// Wait until Vulkan rendering is finished and the texture can be written to
|
||||
vkWaitForFences(get_device().get_handle(), 1, &rendering_finished_fence, VK_TRUE, std::numeric_limits<uint64_t>::max());
|
||||
vkResetFences(get_device().get_handle(), 1, &rendering_finished_fence);
|
||||
|
||||
// Fill the texture using OpenCL
|
||||
run_texture_generation();
|
||||
|
||||
// Wait until the texture is filled
|
||||
// Using synchronization primitives would be better, but corresponding OpenCL extensions are not yet available
|
||||
clFlush(cl_data->command_queue);
|
||||
clFinish(cl_data->command_queue);
|
||||
|
||||
// Display the texture using Vulkan
|
||||
ApiVulkanSample::prepare_frame();
|
||||
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &draw_cmd_buffers[current_buffer];
|
||||
|
||||
VK_CHECK(vkQueueSubmit(queue, 1, &submit_info, rendering_finished_fence));
|
||||
|
||||
ApiVulkanSample::submit_frame();
|
||||
}
|
||||
|
||||
void OpenCLInteropArm::view_changed()
|
||||
{
|
||||
update_uniform_buffers();
|
||||
}
|
||||
|
||||
void OpenCLInteropArm::build_command_buffers()
|
||||
{
|
||||
VkCommandBufferBeginInfo command_buffer_begin_info = vkb::initializers::command_buffer_begin_info();
|
||||
|
||||
VkClearValue clear_values[2];
|
||||
clear_values[0].color = default_clear_color;
|
||||
clear_values[1].depthStencil = {0.0f, 0};
|
||||
|
||||
VkRenderPassBeginInfo render_pass_begin_info = vkb::initializers::render_pass_begin_info();
|
||||
render_pass_begin_info.renderPass = render_pass;
|
||||
render_pass_begin_info.renderArea.offset.x = 0;
|
||||
render_pass_begin_info.renderArea.offset.y = 0;
|
||||
render_pass_begin_info.renderArea.extent.width = width;
|
||||
render_pass_begin_info.renderArea.extent.height = height;
|
||||
render_pass_begin_info.clearValueCount = 2;
|
||||
render_pass_begin_info.pClearValues = clear_values;
|
||||
|
||||
for (int32_t i = 0; i < draw_cmd_buffers.size(); i++)
|
||||
{
|
||||
render_pass_begin_info.framebuffer = framebuffers[i];
|
||||
|
||||
VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffers[i], &command_buffer_begin_info));
|
||||
|
||||
vkCmdBeginRenderPass(draw_cmd_buffers[i], &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
VkViewport viewport = vkb::initializers::viewport((float) width, (float) height, 0.0f, 1.0f);
|
||||
vkCmdSetViewport(draw_cmd_buffers[i], 0, 1, &viewport);
|
||||
|
||||
VkRect2D scissor = vkb::initializers::rect2D(static_cast<int32_t>(width), static_cast<int32_t>(height), 0, 0);
|
||||
vkCmdSetScissor(draw_cmd_buffers[i], 0, 1, &scissor);
|
||||
|
||||
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, 0, 1, &descriptor_set, 0, nullptr);
|
||||
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
|
||||
VkDeviceSize offsets[1] = {0};
|
||||
vkCmdBindVertexBuffers(draw_cmd_buffers[i], 0, 1, vertex_buffer->get(), offsets);
|
||||
vkCmdBindIndexBuffer(draw_cmd_buffers[i], index_buffer->get_handle(), 0, VK_INDEX_TYPE_UINT32);
|
||||
|
||||
vkCmdDrawIndexed(draw_cmd_buffers[i], index_count, 1, 0, 0, 0);
|
||||
|
||||
draw_ui(draw_cmd_buffers[i]);
|
||||
|
||||
vkCmdEndRenderPass(draw_cmd_buffers[i]);
|
||||
|
||||
VK_CHECK(vkEndCommandBuffer(draw_cmd_buffers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void OpenCLInteropArm::generate_quad()
|
||||
{
|
||||
std::vector<VertexStructure> vertices =
|
||||
{
|
||||
{{1.0f, 1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}},
|
||||
{{-1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}},
|
||||
{{-1.0f, -1.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}},
|
||||
{{1.0f, -1.0f, 0.0f}, {1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}};
|
||||
|
||||
std::vector<uint32_t> indices = {0, 1, 2, 2, 3, 0};
|
||||
index_count = static_cast<uint32_t>(indices.size());
|
||||
|
||||
auto vertex_buffer_size = vkb::to_u32(vertices.size() * sizeof(VertexStructure));
|
||||
auto index_buffer_size = vkb::to_u32(indices.size() * sizeof(uint32_t));
|
||||
|
||||
// Create buffers
|
||||
// For the sake of simplicity we won't stage the vertex data to the gpu memory
|
||||
// Vertex buffer
|
||||
vertex_buffer = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
vertex_buffer_size,
|
||||
VK_BUFFER_USAGE_TRANSFER_DST_BIT |
|
||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
vertex_buffer->update(vertices.data(), vertex_buffer_size);
|
||||
|
||||
index_buffer = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
index_buffer_size,
|
||||
VK_BUFFER_USAGE_TRANSFER_DST_BIT |
|
||||
VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
index_buffer->update(indices.data(), index_buffer_size);
|
||||
}
|
||||
|
||||
void OpenCLInteropArm::setup_descriptor_pool()
|
||||
{
|
||||
std::vector<VkDescriptorPoolSize> pool_sizes =
|
||||
{
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
|
||||
vkb::initializers::descriptor_pool_size(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1)};
|
||||
|
||||
VkDescriptorPoolCreateInfo descriptor_pool_create_info = vkb::initializers::descriptor_pool_create_info(static_cast<uint32_t>(pool_sizes.size()),
|
||||
pool_sizes.data(),
|
||||
2);
|
||||
|
||||
VK_CHECK(vkCreateDescriptorPool(get_device().get_handle(), &descriptor_pool_create_info, nullptr, &descriptor_pool));
|
||||
}
|
||||
|
||||
void OpenCLInteropArm::setup_descriptor_set_layout()
|
||||
{
|
||||
std::vector<VkDescriptorSetLayoutBinding> set_layout_bindings{
|
||||
// Binding 0 : Vertex shader uniform buffer
|
||||
vkb::initializers::descriptor_set_layout_binding(
|
||||
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
VK_SHADER_STAGE_VERTEX_BIT,
|
||||
0),
|
||||
// Binding 1 : Fragment shader image sampler
|
||||
vkb::initializers::descriptor_set_layout_binding(
|
||||
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
1),
|
||||
};
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo descriptor_layout =
|
||||
vkb::initializers::descriptor_set_layout_create_info(
|
||||
set_layout_bindings.data(),
|
||||
vkb::to_u32(set_layout_bindings.size()));
|
||||
|
||||
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &descriptor_layout, nullptr,
|
||||
&descriptor_set_layout));
|
||||
|
||||
VkPipelineLayoutCreateInfo pipeline_layout_create_info = vkb::initializers::pipeline_layout_create_info(
|
||||
&descriptor_set_layout, 1);
|
||||
|
||||
VK_CHECK(
|
||||
vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr,
|
||||
&pipeline_layout));
|
||||
}
|
||||
|
||||
void OpenCLInteropArm::setup_descriptor_set()
|
||||
{
|
||||
VkDescriptorSetAllocateInfo alloc_info =
|
||||
vkb::initializers::descriptor_set_allocate_info(
|
||||
descriptor_pool,
|
||||
&descriptor_set_layout,
|
||||
1);
|
||||
|
||||
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &alloc_info, &descriptor_set));
|
||||
|
||||
VkDescriptorBufferInfo buffer_descriptor = create_descriptor(*uniform_buffer_vs);
|
||||
|
||||
// Setup a descriptor image info for the current texture to be used as a combined image sampler
|
||||
VkDescriptorImageInfo image_descriptor;
|
||||
image_descriptor.imageView = shared_texture.view;
|
||||
image_descriptor.sampler = shared_texture.sampler;
|
||||
image_descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
|
||||
std::vector<VkWriteDescriptorSet> write_descriptor_sets =
|
||||
{
|
||||
// Binding 0 : Vertex shader uniform buffer
|
||||
vkb::initializers::write_descriptor_set(
|
||||
descriptor_set,
|
||||
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
0,
|
||||
&buffer_descriptor),
|
||||
// Binding 1 : Fragment shader texture sampler
|
||||
// Fragment shader: layout (binding = 1) uniform sampler2D samplerColor;
|
||||
vkb::initializers::write_descriptor_set(
|
||||
descriptor_set,
|
||||
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, // The descriptor set will use a combined image sampler (sampler and image could be split)
|
||||
1, // Shader binding point 1
|
||||
&image_descriptor) // Pointer to the descriptor image for our texture
|
||||
};
|
||||
|
||||
vkUpdateDescriptorSets(get_device().get_handle(), vkb::to_u32(write_descriptor_sets.size()),
|
||||
write_descriptor_sets.data(), 0, nullptr);
|
||||
}
|
||||
|
||||
void OpenCLInteropArm::prepare_pipelines()
|
||||
{
|
||||
VkPipelineInputAssemblyStateCreateInfo input_assembly_state =
|
||||
vkb::initializers::pipeline_input_assembly_state_create_info(
|
||||
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
|
||||
0,
|
||||
VK_FALSE);
|
||||
|
||||
VkPipelineRasterizationStateCreateInfo rasterization_state =
|
||||
vkb::initializers::pipeline_rasterization_state_create_info(
|
||||
VK_POLYGON_MODE_FILL,
|
||||
VK_CULL_MODE_NONE,
|
||||
VK_FRONT_FACE_COUNTER_CLOCKWISE,
|
||||
0);
|
||||
|
||||
VkPipelineColorBlendAttachmentState blend_attachment_state =
|
||||
vkb::initializers::pipeline_color_blend_attachment_state(
|
||||
0xf,
|
||||
VK_FALSE);
|
||||
|
||||
VkPipelineColorBlendStateCreateInfo color_blend_state =
|
||||
vkb::initializers::pipeline_color_blend_state_create_info(
|
||||
1,
|
||||
&blend_attachment_state);
|
||||
|
||||
// Note: Using reversed depth-buffer for increased precision, so Greater depth values are kept
|
||||
VkPipelineDepthStencilStateCreateInfo depth_stencil_state =
|
||||
vkb::initializers::pipeline_depth_stencil_state_create_info(
|
||||
VK_TRUE,
|
||||
VK_TRUE,
|
||||
VK_COMPARE_OP_GREATER);
|
||||
|
||||
VkPipelineViewportStateCreateInfo viewport_state =
|
||||
vkb::initializers::pipeline_viewport_state_create_info(1, 1, 0);
|
||||
|
||||
VkPipelineMultisampleStateCreateInfo multisample_state =
|
||||
vkb::initializers::pipeline_multisample_state_create_info(
|
||||
VK_SAMPLE_COUNT_1_BIT,
|
||||
0);
|
||||
|
||||
std::vector<VkDynamicState> dynamic_state_enables = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR};
|
||||
|
||||
VkPipelineDynamicStateCreateInfo dynamic_state =
|
||||
vkb::initializers::pipeline_dynamic_state_create_info(
|
||||
dynamic_state_enables.data(),
|
||||
vkb::to_u32(dynamic_state_enables.size()),
|
||||
0);
|
||||
|
||||
// Load shaders
|
||||
std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages{};
|
||||
|
||||
shader_stages[0] = load_shader("texture_loading/texture.vert", VK_SHADER_STAGE_VERTEX_BIT);
|
||||
shader_stages[1] = load_shader("texture_loading/texture.frag", VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
// Vertex bindings and attributes
|
||||
const std::vector<VkVertexInputBindingDescription> vertex_input_bindings = {
|
||||
vkb::initializers::vertex_input_binding_description(0, sizeof(VertexStructure),
|
||||
VK_VERTEX_INPUT_RATE_VERTEX),
|
||||
};
|
||||
const std::vector<VkVertexInputAttributeDescription> vertex_input_attributes = {
|
||||
vkb::initializers::vertex_input_attribute_description(0, 0, VK_FORMAT_R32G32B32_SFLOAT,
|
||||
offsetof(VertexStructure, pos)),
|
||||
vkb::initializers::vertex_input_attribute_description(0, 1, VK_FORMAT_R32G32_SFLOAT,
|
||||
offsetof(VertexStructure, uv)),
|
||||
vkb::initializers::vertex_input_attribute_description(0, 2, VK_FORMAT_R32G32B32_SFLOAT,
|
||||
offsetof(VertexStructure,
|
||||
normal)),
|
||||
};
|
||||
VkPipelineVertexInputStateCreateInfo vertex_input_state = vkb::initializers::pipeline_vertex_input_state_create_info();
|
||||
vertex_input_state.vertexBindingDescriptionCount = vkb::to_u32(vertex_input_bindings.size());
|
||||
vertex_input_state.pVertexBindingDescriptions = vertex_input_bindings.data();
|
||||
vertex_input_state.vertexAttributeDescriptionCount = vkb::to_u32(
|
||||
vertex_input_attributes.size());
|
||||
vertex_input_state.pVertexAttributeDescriptions = vertex_input_attributes.data();
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipeline_create_info =
|
||||
vkb::initializers::pipeline_create_info(
|
||||
pipeline_layout,
|
||||
render_pass,
|
||||
0);
|
||||
|
||||
pipeline_create_info.pVertexInputState = &vertex_input_state;
|
||||
pipeline_create_info.pInputAssemblyState = &input_assembly_state;
|
||||
pipeline_create_info.pRasterizationState = &rasterization_state;
|
||||
pipeline_create_info.pColorBlendState = &color_blend_state;
|
||||
pipeline_create_info.pMultisampleState = &multisample_state;
|
||||
pipeline_create_info.pViewportState = &viewport_state;
|
||||
pipeline_create_info.pDepthStencilState = &depth_stencil_state;
|
||||
pipeline_create_info.pDynamicState = &dynamic_state;
|
||||
pipeline_create_info.stageCount = vkb::to_u32(shader_stages.size());
|
||||
pipeline_create_info.pStages = shader_stages.data();
|
||||
|
||||
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1,
|
||||
&pipeline_create_info, nullptr, &pipeline));
|
||||
}
|
||||
|
||||
void OpenCLInteropArm::prepare_uniform_buffers()
|
||||
{
|
||||
// Vertex shader uniform buffer block
|
||||
uniform_buffer_vs = std::make_unique<vkb::core::BufferC>(get_device(),
|
||||
sizeof(ubo_vs),
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
|
||||
update_uniform_buffers();
|
||||
}
|
||||
|
||||
void OpenCLInteropArm::update_uniform_buffers()
|
||||
{
|
||||
// Vertex shader
|
||||
ubo_vs.projection = glm::perspective(glm::radians(60.0f), (float) width / (float) height, 0.001f, 256.0f);
|
||||
|
||||
glm::mat4 view_matrix = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, zoom));
|
||||
|
||||
ubo_vs.model = view_matrix * glm::translate(glm::mat4(1.0f), camera_pos);
|
||||
ubo_vs.model = glm::rotate(ubo_vs.model, glm::radians(rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
ubo_vs.model = glm::rotate(ubo_vs.model, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
ubo_vs.model = glm::rotate(ubo_vs.model, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
|
||||
ubo_vs.view_pos = glm::vec4(0.0f, 0.0f, -zoom, 0.0f);
|
||||
|
||||
uniform_buffer_vs->convert_and_update(ubo_vs);
|
||||
}
|
||||
|
||||
void OpenCLInteropArm::prepare_shared_resources()
|
||||
{
|
||||
// This texture will be shared between both APIs: OpenCL fills it and Vulkan uses it for rendering
|
||||
shared_texture.width = 256;
|
||||
shared_texture.height = 256;
|
||||
shared_texture.depth = 1;
|
||||
|
||||
auto device_handle = get_device().get_handle();
|
||||
|
||||
// Setting up Vulkan resources (image, memory, image view and sampler)
|
||||
|
||||
// When creating a VkImage object we need to tell explicitly that it will be backed by external memory
|
||||
// In this case it's Android Hardware Buffer, so we specify the handle type accordingly
|
||||
VkExternalMemoryImageCreateInfo external_memory_image_create_info;
|
||||
external_memory_image_create_info.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
|
||||
external_memory_image_create_info.pNext = nullptr,
|
||||
external_memory_image_create_info.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID;
|
||||
|
||||
VkImageCreateInfo image_create_info = vkb::initializers::image_create_info();
|
||||
image_create_info.pNext = &external_memory_image_create_info;
|
||||
image_create_info.imageType = VK_IMAGE_TYPE_2D;
|
||||
image_create_info.format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
image_create_info.mipLevels = 1;
|
||||
image_create_info.arrayLayers = 1;
|
||||
image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
image_create_info.tiling = VK_IMAGE_TILING_LINEAR;
|
||||
image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
image_create_info.extent = {shared_texture.width, shared_texture.height, shared_texture.depth};
|
||||
image_create_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
VK_CHECK(vkCreateImage(get_device().get_handle(), &image_create_info, nullptr, &shared_texture.image));
|
||||
|
||||
// Memory will be allocated specifically for this VkImage object
|
||||
VkMemoryDedicatedAllocateInfo dedicated_allocate_info;
|
||||
dedicated_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;
|
||||
dedicated_allocate_info.pNext = nullptr;
|
||||
dedicated_allocate_info.buffer = VK_NULL_HANDLE;
|
||||
dedicated_allocate_info.image = shared_texture.image;
|
||||
|
||||
VkMemoryRequirements memory_requirements{};
|
||||
vkGetImageMemoryRequirements(get_device().get_handle(), shared_texture.image, &memory_requirements);
|
||||
|
||||
// In order to export an external handle later, we need to tell it explicitly during memory allocation
|
||||
VkExportMemoryAllocateInfo export_memory_allocate_Info;
|
||||
export_memory_allocate_Info.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO;
|
||||
export_memory_allocate_Info.pNext = &dedicated_allocate_info;
|
||||
export_memory_allocate_Info.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID;
|
||||
|
||||
VkMemoryAllocateInfo memory_allocate_info = vkb::initializers::memory_allocate_info();
|
||||
memory_allocate_info.pNext = &export_memory_allocate_Info;
|
||||
memory_allocate_info.allocationSize = 0;
|
||||
memory_allocate_info.memoryTypeIndex = get_device().get_gpu().get_memory_type(memory_requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
|
||||
VK_CHECK(vkAllocateMemory(device_handle, &memory_allocate_info, nullptr, &shared_texture.memory));
|
||||
VK_CHECK(vkBindImageMemory(device_handle, shared_texture.image, shared_texture.memory, 0));
|
||||
|
||||
// Once the memory is allocated and bound, we can get an Android Hardware Buffer handle
|
||||
VkMemoryGetAndroidHardwareBufferInfoANDROID info;
|
||||
info.sType = VK_STRUCTURE_TYPE_MEMORY_GET_ANDROID_HARDWARE_BUFFER_INFO_ANDROID;
|
||||
info.pNext = nullptr;
|
||||
info.memory = shared_texture.memory;
|
||||
VK_CHECK(vkGetMemoryAndroidHardwareBufferANDROID(device_handle, &info, &shared_texture.hardware_buffer));
|
||||
|
||||
// Calculate valid filter and mipmap modes
|
||||
VkFilter filter = VK_FILTER_LINEAR;
|
||||
VkSamplerMipmapMode mipmap_mode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||||
vkb::make_filters_valid(get_device().get_gpu().get_handle(), image_create_info.format, &filter, &mipmap_mode);
|
||||
|
||||
VkSamplerCreateInfo sampler_create_info{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
|
||||
sampler_create_info.magFilter = filter;
|
||||
sampler_create_info.minFilter = filter;
|
||||
sampler_create_info.mipmapMode = mipmap_mode;
|
||||
sampler_create_info.maxLod = (float) 1;
|
||||
sampler_create_info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
||||
vkCreateSampler(device_handle, &sampler_create_info, nullptr, &shared_texture.sampler);
|
||||
|
||||
VkImageViewCreateInfo view_create_info{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
||||
view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
view_create_info.image = shared_texture.image;
|
||||
view_create_info.format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
view_create_info.subresourceRange = VkImageSubresourceRange{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
vkCreateImageView(device_handle, &view_create_info, nullptr, &shared_texture.view);
|
||||
|
||||
VkCommandBuffer copy_command = get_device().create_command_buffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
|
||||
|
||||
VkImageSubresourceRange subresource_range = {};
|
||||
subresource_range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
subresource_range.baseMipLevel = 0;
|
||||
subresource_range.levelCount = 1;
|
||||
subresource_range.layerCount = 1;
|
||||
|
||||
VkImageMemoryBarrier image_memory_barrier = vkb::initializers::image_memory_barrier();
|
||||
image_memory_barrier.image = shared_texture.image;
|
||||
image_memory_barrier.subresourceRange = subresource_range;
|
||||
image_memory_barrier.srcAccessMask = 0;
|
||||
image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
|
||||
vkCmdPipelineBarrier(
|
||||
copy_command,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||
0,
|
||||
0, nullptr,
|
||||
0, nullptr,
|
||||
1, &image_memory_barrier);
|
||||
|
||||
get_device().flush_command_buffer(copy_command, queue, true);
|
||||
|
||||
// Setting up OpenCL resources
|
||||
|
||||
// In the list of properties CL_IMPORT_TYPE_ARM is set to Android Hardware Buffer
|
||||
// The list is terminated with 0
|
||||
const cl_import_properties_arm import_properties[3] = {
|
||||
CL_IMPORT_TYPE_ARM, CL_IMPORT_TYPE_ANDROID_HARDWARE_BUFFER_ARM,
|
||||
0};
|
||||
|
||||
cl_int result = CL_SUCCESS;
|
||||
cl_data->image = clImportMemoryARM(cl_data->context,
|
||||
CL_MEM_READ_WRITE,
|
||||
import_properties,
|
||||
shared_texture.hardware_buffer,
|
||||
CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM,
|
||||
&result);
|
||||
|
||||
if (result != CL_SUCCESS)
|
||||
{
|
||||
LOGE("Cannot import OpenCL memory, error code: {}.", result);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> get_available_open_cl_extensions(cl_platform_id platform_id)
|
||||
{
|
||||
size_t extensions_info_size = 0;
|
||||
clGetPlatformInfo(platform_id, CL_PLATFORM_EXTENSIONS, 0, nullptr, &extensions_info_size);
|
||||
|
||||
std::vector<char> extensions_info(extensions_info_size, '\0');
|
||||
clGetPlatformInfo(platform_id, CL_PLATFORM_EXTENSIONS, extensions_info_size, extensions_info.data(), nullptr);
|
||||
|
||||
std::istrstream extensions_info_stream(extensions_info.data(), extensions_info.size());
|
||||
return std::vector<std::string>(std::istream_iterator<std::string>{extensions_info_stream}, std::istream_iterator<std::string>());
|
||||
}
|
||||
|
||||
void OpenCLInteropArm::prepare_open_cl_resources()
|
||||
{
|
||||
cl_platform_id platform_id = load_opencl();
|
||||
if (platform_id == nullptr)
|
||||
{
|
||||
LOGE("Cannot load OpenCL library.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto available_extensions = get_available_open_cl_extensions(platform_id);
|
||||
std::vector<std::string> required_extensions{
|
||||
"cl_arm_import_memory",
|
||||
"cl_arm_import_memory_android_hardware_buffer"};
|
||||
|
||||
for (auto extension : required_extensions)
|
||||
{
|
||||
if (std::find(available_extensions.begin(), available_extensions.end(), extension) == available_extensions.end())
|
||||
{
|
||||
LOGE("Required OpenCL extension '{}' is not available.", extension);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cl_uint num_devices;
|
||||
clGetDeviceIDs(platform_id, CL_DEVICE_TYPE_DEFAULT, 1, &cl_data->device_id, &num_devices);
|
||||
|
||||
cl_int result = CL_SUCCESS;
|
||||
cl_data->context = clCreateContext(NULL, 1, &cl_data->device_id, NULL, NULL, &result);
|
||||
|
||||
if (result != CL_SUCCESS)
|
||||
{
|
||||
LOGE("Cannot create OpenCL context.");
|
||||
return;
|
||||
}
|
||||
|
||||
cl_data->command_queue = clCreateCommandQueue(cl_data->context, cl_data->device_id, 0, &result);
|
||||
|
||||
std::string kernel_source = vkb::fs::read_text_file("open_cl_interop_arm/procedural_texture.cl");
|
||||
auto kernel_source_data = kernel_source.c_str();
|
||||
size_t kernel_source_size = kernel_source.size();
|
||||
|
||||
cl_data->program = clCreateProgramWithSource(cl_data->context, 1, &kernel_source_data, &kernel_source_size, &result);
|
||||
clBuildProgram(cl_data->program, 1, &cl_data->device_id, NULL, NULL, NULL);
|
||||
cl_data->kernel = clCreateKernel(cl_data->program, "generate_texture", &result);
|
||||
|
||||
if (result != CL_SUCCESS)
|
||||
{
|
||||
LOGE("Cannot create OpenCL kernel");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void OpenCLInteropArm::run_texture_generation()
|
||||
{
|
||||
clSetKernelArg(cl_data->kernel, 0, sizeof(cl_mem), &cl_data->image);
|
||||
clSetKernelArg(cl_data->kernel, 1, sizeof(float), &total_time_passed);
|
||||
|
||||
std::array<size_t, 2> global_size = {shared_texture.width, shared_texture.height};
|
||||
std::array<size_t, 2> local_size = {16, 16};
|
||||
|
||||
cl_int result = clEnqueueNDRangeKernel(cl_data->command_queue,
|
||||
cl_data->kernel,
|
||||
global_size.size(),
|
||||
NULL,
|
||||
global_size.data(),
|
||||
local_size.data(),
|
||||
0, NULL, NULL);
|
||||
|
||||
if (result != CL_SUCCESS)
|
||||
{
|
||||
LOGE("Cannot execute kernel, error code: {}", result);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::VulkanSample<vkb::BindingType::C>> create_open_cl_interop_arm()
|
||||
{
|
||||
return std::make_unique<OpenCLInteropArm>();
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/* Copyright (c) 2021-2024, Arm Limited and Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 the "License";
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
#include "rendering/render_pipeline.h"
|
||||
#include "scene_graph/components/camera.h"
|
||||
|
||||
struct CLData;
|
||||
typedef struct AHardwareBuffer AHardwareBuffer;
|
||||
|
||||
class OpenCLInteropArm : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
OpenCLInteropArm();
|
||||
~OpenCLInteropArm() override;
|
||||
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
|
||||
void render(float delta_time) override;
|
||||
void view_changed() override;
|
||||
void build_command_buffers() override;
|
||||
|
||||
private:
|
||||
void prepare_pipelines();
|
||||
void prepare_open_cl_resources();
|
||||
void prepare_shared_resources();
|
||||
void generate_quad();
|
||||
void setup_descriptor_pool();
|
||||
void setup_descriptor_set_layout();
|
||||
void setup_descriptor_set();
|
||||
void prepare_uniform_buffers();
|
||||
void update_uniform_buffers();
|
||||
void run_texture_generation();
|
||||
|
||||
struct VertexStructure
|
||||
{
|
||||
float pos[3];
|
||||
float uv[2];
|
||||
float normal[3];
|
||||
};
|
||||
|
||||
struct SharedTexture
|
||||
{
|
||||
uint32_t width{0};
|
||||
uint32_t height{0};
|
||||
uint32_t depth{0};
|
||||
|
||||
VkImage image{VK_NULL_HANDLE};
|
||||
VkDeviceMemory memory{VK_NULL_HANDLE};
|
||||
VkDeviceSize size{0};
|
||||
VkDeviceSize allocationSize{0};
|
||||
VkSampler sampler{VK_NULL_HANDLE};
|
||||
VkImageView view{VK_NULL_HANDLE};
|
||||
|
||||
AHardwareBuffer *hardware_buffer{nullptr};
|
||||
} shared_texture;
|
||||
|
||||
struct UniformBufferData
|
||||
{
|
||||
glm::mat4 projection;
|
||||
glm::mat4 model;
|
||||
glm::vec4 view_pos;
|
||||
} ubo_vs;
|
||||
|
||||
VkPipeline pipeline{VK_NULL_HANDLE};
|
||||
VkPipelineLayout pipeline_layout{VK_NULL_HANDLE};
|
||||
VkDescriptorSet descriptor_set{VK_NULL_HANDLE};
|
||||
VkDescriptorSetLayout descriptor_set_layout{VK_NULL_HANDLE};
|
||||
|
||||
std::unique_ptr<vkb::core::BufferC> vertex_buffer;
|
||||
std::unique_ptr<vkb::core::BufferC> index_buffer;
|
||||
uint32_t index_count{0};
|
||||
std::unique_ptr<vkb::core::BufferC> uniform_buffer_vs;
|
||||
|
||||
VkFence rendering_finished_fence{VK_NULL_HANDLE};
|
||||
|
||||
float total_time_passed{0};
|
||||
|
||||
CLData *cl_data{nullptr};
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::VulkanSample<vkb::BindingType::C>> create_open_cl_interop_arm();
|
||||
Reference in New Issue
Block a user