1
0

chore: initial commit

Signed-off-by: Alan Brault <alan.brault@visus.io>
This commit is contained in:
2025-11-17 08:02:58 -05:00
commit 3346eecb52
77 changed files with 4246 additions and 0 deletions

1
lib/vulkan/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,56 @@
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
}
android {
namespace = "io.visus.solanim.vulkan"
compileSdk {
version = release(36)
}
defaultConfig {
minSdk = 35
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
externalNativeBuild {
cmake {
arguments += listOf("-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON")
cppFlags("-std=c++17")
}
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
externalNativeBuild {
cmake {
path("src/main/cpp/CMakeLists.txt")
version = "3.22.1"
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
}

View File

21
lib/vulkan/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,24 @@
package io.visus.solanim.vulkan
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("io.visus.solanim.vulkan.test", appContext.packageName)
}
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@@ -0,0 +1,41 @@
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html.
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.
# Sets the minimum CMake version required for this project.
cmake_minimum_required(VERSION 3.22.1)
# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
# Since this is the top level CMakeLists.txt, the project name is also accessible
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
# build script scope).
project("vulkan")
if (${CMAKE_SYSTEM_NAME} STREQUAL "Android")
# ... other Android specific settings ...
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
endif()
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#
# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME}
# is preferred for the same purpose.
#
# In order to load a library into your app from Java/Kotlin, you must call
# System.loadLibrary() and pass the name of the library defined here;
# for GameActivity/NativeActivity derived applications, the same library name must be
# used in the AndroidManifest.xml file.
add_library(${CMAKE_PROJECT_NAME} SHARED
# List C/C++ source files with relative paths to this CMakeLists.txt.
vulkan.cpp)
# Specifies libraries CMake should link to your target library. You
# can link libraries from various origins, such as libraries defined in this
# build script, prebuilt third-party libraries, or Android system libraries.
target_link_libraries(${CMAKE_PROJECT_NAME}
android
log)

View File

@@ -0,0 +1,262 @@
#ifndef VULKANRENDERER_H
#define VULKANRENDERER_H
#include <android/asset_manager.h>
#include <android/log.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include <vulkan/vulkan.h>
#include <vulkan/vulkan_android.h>
#include <cassert>
#include <optional>
#include <memory>
#include <set>
#include <vector>
struct QueueFamilyIndices {
std::optional<uint32_t> graphicsFamily;
std::optional<uint32_t> presentFamily;
bool isComplete() {
return graphicsFamily.has_value() && presentFamily.has_value();
}
};
struct SwapChainSupportDetails {
VkSurfaceCapabilitiesKHR capabilities;
std::vector<VkSurfaceFormatKHR> formats;
std::vector<VkPresentModeKHR> presentModes;
};
struct ANativeWindowDestroyer {
void operator()(ANativeWindow* window) const {
if (window) {
ANativeWindow_release(window);
}
}
};
class VulkanRenderer {
#define LOG_TAG "VulkanRenderer"
#ifdef NDEBUG
#define LOGI(...) ((void)0)
#define LOGE(...) ((void)0)
#else
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#endif
#define VK_CHECK(callback) { \
do { \
VkResult result = callback; \
if (result != VK_SUCCESS) { \
LOGE("Vulkan error: %d, at %s:%d", result, __FILE__, __LINE__); \
abort(); \
} \
} while (0); \
} \
public:
void initialize();
bool isInitialized = false;
private:
std::unique_ptr<ANativeWindow, ANativeWindowDestroyer> window_;
const std::vector<const char *> deviceExtensions_ = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
VkInstance instance_;
VkSurfaceKHR surface_;
VkPhysicalDevice physicalDevice_ = VK_NULL_HANDLE;
VkDevice device_;
VkQueue graphicsQueue_;
VkQueue presentQueue_;
void createInstance();
void createLogicalDeviceAndQueue();
void createSurface();
bool checkDeviceExtensionSupport(VkPhysicalDevice device);
QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) const;
bool isDeviceSuitable(VkPhysicalDevice device);
void pickPhysicalDevice();
static std::vector<const char*> getRequiredExtensions();
};
std::vector<const char*> VulkanRenderer::getRequiredExtensions() {
std::vector<const char *> extensions;
extensions.push_back("VK_KHR_surface");
extensions.push_back("VK_KHR_android_surface");
return extensions;
}
void VulkanRenderer::initialize() {
createInstance();
createSurface();
pickPhysicalDevice();
createLogicalDeviceAndQueue();
isInitialized = true;
LOGI("VulkanRenderer initialized successfully.");
}
void VulkanRenderer::createInstance() {
std::vector<const char*> requiredExtensions = getRequiredExtensions();
VkApplicationInfo appInfo{
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pApplicationName = "Vulkan Renderer",
.applicationVersion = VK_MAKE_VERSION(1, 0, 0),
.pEngineName = "No Engine",
.engineVersion = VK_MAKE_VERSION(1, 0, 0),
.apiVersion = VK_API_VERSION_1_1
};
VkInstanceCreateInfo createInfo{
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pNext = nullptr,
.pApplicationInfo = &appInfo,
.enabledLayerCount = 0,
.enabledExtensionCount = (uint32_t)requiredExtensions.size(),
.ppEnabledExtensionNames = requiredExtensions.data(),
};
VK_CHECK(vkCreateInstance(&createInfo, nullptr, &instance_))
}
void VulkanRenderer::createLogicalDeviceAndQueue() {
QueueFamilyIndices indices = findQueueFamilies(physicalDevice_);
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
std::set<uint32_t> uniqueQueueFamilies = {
indices.graphicsFamily.value(),
indices.presentFamily.value()
};
float queuePriority = 1.0f;
for (uint32_t queueFamily : uniqueQueueFamilies) {
VkDeviceQueueCreateInfo queueCreateInfo{
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = queueFamily,
.queueCount = 1,
.pQueuePriorities = &queuePriority,
};
queueCreateInfos.push_back(queueCreateInfo);
}
VkPhysicalDeviceFeatures deviceFeatures{};
VkDeviceCreateInfo createInfo{
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size()),
.pQueueCreateInfos = queueCreateInfos.data(),
.enabledLayerCount = 0,
.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions_.size()),
.ppEnabledExtensionNames = deviceExtensions_.data(),
.pEnabledFeatures = &deviceFeatures,
};
VK_CHECK(vkCreateDevice(physicalDevice_, &createInfo, nullptr, &device_))
vkGetDeviceQueue(device_, indices.graphicsFamily.value(), 0, &graphicsQueue_);
vkGetDeviceQueue(device_, indices.presentFamily.value(), 0, &presentQueue_);
}
void VulkanRenderer::createSurface() {
assert(window_ != nullptr);
const VkAndroidSurfaceCreateInfoKHR createInfo{
.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR,
.pNext = nullptr,
.flags = 0,
.window = window_.get(),
};
VK_CHECK(vkCreateAndroidSurfaceKHR(instance_, &createInfo, nullptr, &surface_))
}
bool VulkanRenderer::checkDeviceExtensionSupport(VkPhysicalDevice device) {
uint32_t extensionCount;
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount,
nullptr);
std::vector<VkExtensionProperties> availableExtensions(extensionCount);
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount,
availableExtensions.data());
std::set<std::string> requiredExtensions(deviceExtensions_.begin(), deviceExtensions_.end());
for (const VkExtensionProperties &extension : availableExtensions) {
requiredExtensions.erase(extension.extensionName);
}
return requiredExtensions.empty();
}
QueueFamilyIndices VulkanRenderer::findQueueFamilies(VkPhysicalDevice device) const {
QueueFamilyIndices indices;
uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount,
queueFamilies.data());
for (uint32_t i = 0; i < queueFamilies.size(); i++) {
if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
indices.graphicsFamily = i;
}
VkBool32 presentSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface_, &presentSupport);
if (presentSupport) {
indices.presentFamily = i;
}
if (indices.isComplete()) {
break;
}
}
return indices;
}
bool VulkanRenderer::isDeviceSuitable(VkPhysicalDevice device) {
QueueFamilyIndices indices = findQueueFamilies(device);
bool extensionsSupported = checkDeviceExtensionSupport(device);
bool swapChainAdequate = false;
return indices.isComplete() && extensionsSupported && swapChainAdequate;
}
void VulkanRenderer::pickPhysicalDevice() {
uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(instance_, &deviceCount, nullptr);
assert(deviceCount > 0);
std::vector<VkPhysicalDevice> devices(deviceCount);
vkEnumeratePhysicalDevices(instance_, &deviceCount, devices.data());
auto it = std::find_if(devices.begin(), devices.end(),
[this](VkPhysicalDevice device) { return isDeviceSuitable(device); });
if (it != devices.end()) {
physicalDevice_ = *it;
} else {
LOGE("Failed to find a suitable GPU!");
}
}
#endif //VULKANRENDERER_H

View File

@@ -0,0 +1,10 @@
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_io_visus_solanim_vulkan_NativeLib_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}

View File

@@ -0,0 +1,17 @@
package io.visus.solanim.vulkan
class NativeLib {
/**
* A native method that is implemented by the 'vulkan' native library,
* which is packaged with this application.
*/
external fun stringFromJNI(): String
companion object {
// Used to load the 'vulkan' library on application startup.
init {
System.loadLibrary("vulkan")
}
}
}

View File

@@ -0,0 +1,17 @@
package io.visus.solanim.vulkan
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}