Vulkan学习(一):Vulkan环境搭建(Windows)&官方教程--(Base code & Instance & Validation layers)

前言

  接触图形也有一段时间了,也差不多玩明白Google FIlament引擎。虽然苹果抛弃了OpenGL,但我还是认为OpenGL依然有着跨平台的优势。 依旧坚持使用OpenGL做开发。但事与愿违,虽然大佬们依然继续维护OpenGL。但渐渐发现,OpenGL的跨平台的优势一步步被Vulkan取代。至少在我能接触的领域中,对Vulkan支持已经大于对OpenGL的支持。为了学习NRD(NVIDIA Real-Time Denoiser),也是正好想拓展一下技术面。撸起袖子,冲鸭!#109。

Vulkan相关资源地址:

  • Vulkan官方网址:https://vulkan-tutorial.com/ (包含教程PDF)
  • Vulkan示例git网址:https://github.com/KhronosGroup/Vulkan-Samples (包含安装教程)
  • CSDN git: https://codechina.csdn.net/mirrors/SaschaWillems/Vulkan (Samples 目录比较好)
  • Vulkan SDK下载:https://vulkan.lunarg.com/
  • 官方教程使用GLFW,下载地址:https://www.glfw.org/
  • 使用到GLM,下载地址:https://github.com/g-truc/glm

Vulkan安装:

Vulkan教程环境相关

  • 下载完Vulkan SDK,直接默认安装即可。(记住安装路径)
  • GLFW可以去官网下载源码,自己用Cmake + VS编译
  • GLM clone下来后直接将头文件引入到项目工程中即可

Vulkan 示例相关

  • git clone --recursive https://github.com/SaschaWillems/Vulkan.git (可以Clone官方示例)
  • 已经Clone示例库,可以更新相关资源
    git submodule init
    git submodule update
  • Clone完成后,安装依赖数据 python download_assets.py or 进入工程双击 download_assets.py
  • 编译示例 cmake -G “Visual Studio 15 2017 Win64” or 手动Cmake+VS编译。(需要最新SDK–10.0.19xx,VS2019–16.9.4, Cmake–3.20.x)(使用过较低版本的环境,没编译成功!)

Vulkan教程学习笔记

  Vulkan教程示例的项目框架搭建,参照官方文档。

Vulkan程序的基本结构

		initWindow(); //初始化窗口
		initVulkan(); //初始化Vulakn相关
		mainLoop();   //渲染循环
		cleanup();    //释放资源

initWindow(GLFW)

		glfwInit();   //初始化GLFW库
		//由于glfw原本是用来创建OpenGL Context的,因此需要使用GLFW_NO_API(不进行OpenGL Context创建)
		glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
		glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); //关闭重置窗口
		//创建窗体
		window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);

initVulkan

		createInstance();//创建Vulkan实例
		void createInstance() {
			//创建实例时检测是否启用验证层
			checkValidationLayerSupport();
			//结构体必须指明类型,pNext指向扩展信息
			VkApplicationInfo appInfo = {};
			//Vulkan驱动程序使用哪些全局扩展和验证,后续后详细说明[代码中] 
			VkInstanceCreateInfo createInfo = {};
			//创建实例
			if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS)
			{
				 throw std::runtime_error("failed to create instance!");
			}
			//支持扩展的数量
			uint32_t extensionCount = 0;
			vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
			//支持的扩展详细信息
			std::vector<VkExtensionProperties> extensionsProperties(extensionCount);
			vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensionsProperties.data());
			//查询扩展的详细信息
			std::cout << "available extensions:" << std::endl;
			for (const auto& extension : extensionsProperties) {
				std::cout << "\t" << extension.extensionName << std::endl;
			}
			
		}
		setupDebugMessenger();//设置调试信息
		void setupDebugMessenger() {
			if (!enableValidationLayers) return;
			VkDebugUtilsMessengerCreateInfoEXT createInfo = {};
			populateDebugMessengerCreateInfo(createInfo);
			//messenger创建信息的填充提取到单独的函数中
			if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr,
				&debugMessenger) != VK_SUCCESS) {
				throw std::runtime_error("failed to set up debug messenger!");
			}
	}	

mainLoop

	//窗口关门,终止渲染
	while (!glfwWindowShouldClose(window)) {
		 glfwPollEvents();
	}

cleanup

	//销毁代理函数
	if (enableValidationLayers) {
		DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
	}
	//销毁实例
	vkDestroyInstance(instance, nullptr);
	//释放GLFW资源
	glfwDestroyWindow(window);
	glfwTerminate();

Vulkan建议:

  1. 创建的Vulkan对象,在不需要的时候必须被销毁。
  2. 在C++教程中,所有Vulkan相关的对象必须显式的创建和销毁。
  3. 在C++教程中,资源管理使用共享指针 std::shared_ptr,RAII机制
  4. Vulkan 使用vkCreateXXX进行创建,vkAllocateXXX进行分配
  5. Vulkan 使用vkDestroyXXX和vkFreeXXX销毁和释放
  6. 对于不同类型的对象,参数通常有所不同,但pAllocato是共享的。这是一个可选参数,允许自定义内存分配指定回调。教程中忽略此参数,而使用nullptr
  7. Vulkan是一个平台无关的API
  8. Vulkan 使用结构体代替函数参数
  9. Vulkan 存储的的是对象的Handle

Validation layers (验证层)

验证层是可选组件,Vulkan函数调用时可以进行附加操作。

验证层中的常见操作有:

  • 依照规范(the specification)检测参数值以发现错误
  • 跟踪对象的创建和销毁,查找资源泄漏
  • 通过跟踪调用的线程来检查线程安全性
  • 将每次调用及其参数记录到标准输出中
  • 跟踪Vulkan调用并进行分析和回放

建议:

  • 验证层在Debug的时候开启,在release的时候关闭
  • 验证层在跨平台编译时,能很好的捕获相关异常
  • 使用验证层,必须将Vulkan SDK安装到系统中才能使用

  Vulkan以前有两种不同类型的验证层:实例验证层和设备验证层。其思想是实例层只检查与全局Vulkan对象(如实例)相关的调用,而特定驱动(device specific)层只检查与特定GPU相关的调用。device specific层现在已被弃用,这意味着实例验证层将应用于所有Vulkan调用。规范文档仍然建议您在设备级别启用验证层以实现兼容性,这是某些实现所必需的。我们只需在逻辑设备级别指定与实例相同的层即可。

Message callback:

vk_layer_settings.txt 是消息层使用的文档

完整代码:

#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>

#include <vulkan/vulkan.h>

#include <iostream>
#include <GLFW/glfw3.h>

#include <vulkan/vulkan.h>

#include <iostream>  //[1]
#include <stdexcept> //[1]异常处理函数
#include <functional>//[1]提供 EXIT_SUCCESS and EXIT_FAILURE 宏指令
#include <cstdlib>
#include <vector>
const int WIDTH = 800;
const int HEIGHT = 600;
//[4]所有有用的标准验证都捆绑到SDK的一个层中,称为VK_LAYER_KHRONOS_validation层。
const std::vector<const char*> validationLayers = {
	"VK_LAYER_KHRONOS_validation"
 };
//[4]验证层Debug时开启
#ifdef NDEBUG
 const bool enableValidationLayers = false;
#else
 const bool enableValidationLayers = true;
#endif


class HelloTriangleApplication
{

public:
	void run() 
	{
		initWindow(); //[2]
		initVulkan(); //[1]初始化Vulakn相关
		mainLoop();   //[1]渲染循环(start rendering frames) 
		cleanup();    //[1]释放资源
	}
private:
	void initWindow() {
		glfwInit();   //[2]初始化GLFW库
		//[2]由于glfw原本是用来创建OpenGL Context的,因此需要使用GLFW_NO_API(不进行OpenGL Context创建)
		glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
		glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); //[2]关闭重置窗口
		//[2]创建窗体
		window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
	}
	void initVulkan() 
	{
		//[3]创建Vulkan实例
		createInstance();
		//[6]
		setupDebugMessenger();
	}

	void mainLoop()
	{
		//[2]窗口关门,终止渲染
		while (!glfwWindowShouldClose(window)) {
			 glfwPollEvents();
		}
	}

	void cleanup() 
	{
		//[6]销毁代理函数
		if (enableValidationLayers) {
			DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
		}
		//[3]销毁实例
		vkDestroyInstance(instance, nullptr);
		//[2]释放资源
		glfwDestroyWindow(window);
		glfwTerminate();
	}
	void createInstance() {

		//[4]创建实例时检测是否启用验证层
		if (enableValidationLayers && !checkValidationLayerSupport()) {
			throw std::runtime_error("validation layers requested, but not available!");
		}

		//[3]well-known graphics engine 
		VkApplicationInfo appInfo = {};
		//[3]结构体必须指明类型,pNext指向拓展信息
		appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
		appInfo.pApplicationName = "Hello Triangle";
		appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
		appInfo.pEngineName = "No Engine";
		appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
		appInfo.apiVersion = VK_API_VERSION_1_0;
		
		//[3]Vulkan驱动程序使用哪些全局扩展和验证,后续后详细说明 
		VkInstanceCreateInfo createInfo = {};
		createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
		createInfo.pApplicationInfo = &appInfo;
		

		//[3]指定全局扩展
		uint32_t glfwExtensionCount = 0;
		const char** glfwExtensions;
		glfwExtensions =
			glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
		createInfo.enabledExtensionCount = glfwExtensionCount;
		createInfo.ppEnabledExtensionNames = glfwExtensions;
		//[3]the global validation layers to enable
		createInfo.enabledLayerCount = 0; //后续有说明
		//[5]验证层信息
		//[5]如果检查成功,那么vkCreateInstance不会返回VK_ERROR_LAYER_NOT_PRESENT错误
		if (enableValidationLayers) {
			createInfo.enabledLayerCount =
				static_cast<uint32_t>(validationLayers.size());
			createInfo.ppEnabledLayerNames = validationLayers.data();
		}
		else {
			createInfo.enabledLayerCount = 0;
		}
		//[5]GLFW
		auto extensions = getRequiredExtensions();
		createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
		createInfo.ppEnabledExtensionNames = extensions.data();

		//[6]重用
		//[6]通过该方式创建一个额外的调试信息,它将在vkCreateInstance和vkDestroyInstance期间自动创建和销毁
		VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo;
		if (enableValidationLayers) {
			createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
			createInfo.ppEnabledLayerNames = validationLayers.data();
			populateDebugMessengerCreateInfo(debugCreateInfo);
			createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo;
		}
		else {
			createInfo.enabledLayerCount = 0;
			createInfo.pNext = nullptr;
		}
		//[6]or
		/*if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
			throw std::runtime_error("failed to set up debug messenger!");
		}*/
		//[3]	VK_SUCCESS or Error Code
		//[3]VkResult result = vkCreateInstance(&createInfo, nullptr, &instance);
		//[3]or
		//[3]创建实例
		if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS)
		{
			 throw std::runtime_error("failed to create instance!");
			 /*
			 * //[4]验证层说明,Vulkan每次调用都会进行相应的验证,通过返回值判定函数是否执行成功
			 VkResult vkCreateInstance(
				 const VkInstanceCreateInfo * pCreateInfo,
				 const VkAllocationCallbacks * pAllocator,
				 VkInstance * instance) {

					 if (pCreateInfo == nullptr || instance == nullptr) {
						 log("Null pointer passed to required parameter!");
						 return VK_ERROR_INITIALIZATION_FAILED;

					 }
				 return real_vkCreateInstance(pCreateInfo, pAllocator, instance);
			 }
			 */

		}
		
		//[3]the number of extensions
		//[3]支持扩展的数量
		uint32_t extensionCount = 0;
		vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
		//[3]an array of VkExtensionProperties to store details of the extensions.
		//[3]an array to hold the extension details
		//[3]支持的扩展详细信息
		std::vector<VkExtensionProperties> extensionsProperties(extensionCount);
		vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensionsProperties.data());

		//[3]query the extension details
		//[3]Each VkExtensionProperties struct contains the name and version of an extension.
		//[3]查询扩展的详细信息
		std::cout << "available extensions:" << std::endl;
		for (const auto& extension : extensionsProperties) {
			std::cout << "\t" << extension.extensionName << std::endl;
		}
		
	}
	//[4]list all of the available layers
	//[4]列出所有验证层的信息
	bool checkValidationLayerSupport() {
		
		uint32_t layerCount;
		vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
		
		std::vector<VkLayerProperties> availableLayers(layerCount);
		vkEnumerateInstanceLayerProperties(&layerCount,
		availableLayers.data());
		//[4]查询是否存在验证层信息 layerName = VK_LAYER_KHRONOS_validation
		for (const char* layerName : validationLayers) {
			bool layerFound = false;

			for (const auto& layerProperties : availableLayers) {
				if (strcmp(layerName, layerProperties.layerName) == 0) {
					layerFound = true;
					break;
				}
			}

			if (!layerFound) {
				return false;
			}
		}

		return true;
	}
	//[5]we have to set up a debug messenger with a callback using the VK_EXT_debug_utils extension.
	//[5]我们必须使用VK_EXT_debug_utils扩展,设置一个带有回调的debug messenger。
	std::vector<const char*> getRequiredExtensions() {
		//[5]指定GLFW扩展,但是debug messenger 扩展是有条件添加的
		uint32_t glfwExtensionCount = 0;
		const char** glfwExtensions;
		glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
		std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);
		if (enableValidationLayers) {
			//[5]在这里使用VK_EXT_DEBUG_UTILS_EXTENSION_NAME宏,它等于字符串“VK_EXT_debug_utils”。
			//[5]使用此宏可以避免输入错误
			extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);

		}
		return extensions;
	}
	//[5]Add a new static member function called debugCallback with 
	//[5]	the PFN_vkDebugUtilsMessengerCallbackEXT prototype.
	//[5]使用PFN_vkDebugUtilsMessengerCallbackEXT属性添加一个静态函数
	//[5]The VKAPI_ATTR and VKAPI_CALL ensure that the function has the
	//[5]	right signature for Vulkan to call it.
	//[5]使用VKAPI_ATTR和VKAPI_CALL 确保函数具有正确的签名,以便Vulkan调用它
	static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
		//[5]VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT 诊断信息
		//[5]VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT 信息性消息,如资源的创建
		//[5]关于行为的消息,其不一定是错误,但很可能是应用程序中的BUG
		//[5]VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
		//[5]关于无效且可能导致崩溃的行为的消息
		//[5]VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT
		//[5]可以使用比较操作来检查消息是否与某个严重性级别相等或更差,例如:
		//[5]if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
		//[5]	// Message is important enough to show
		//[5]}
		VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
		//[6]VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT 发生了一些与规范或性能无关的事件
		//[6]VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT 发生了违反规范或一些可能显示的错误
		//[6]VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT 非最优的方式使用Vulkan
		VkDebugUtilsMessageTypeFlagsEXT messageType,
		//[6]消息本身的详细信息, 包括其重要成员:
		//[6]pMessage 以null结尾的调试消息字符串
		//[6]pObjects 与消息相关的Vulkan对象句柄数组
		//[6]objectCount 数组中的对象数
		//[6]pUserData 包含回调指定的指针,允许将自己设置的数据传递给它。
		const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
		void* pUserData) {
		std::cerr << "validation layer: " << pCallbackData->pMessage <<
			std::endl;

		return VK_FALSE;
		

	}

	void setupDebugMessenger() {
		if (!enableValidationLayers) return;

		VkDebugUtilsMessengerCreateInfoEXT createInfo = {};
		populateDebugMessengerCreateInfo(createInfo);
		//[6] messenger创建信息的填充提取到单独的函数中
		if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr,
			&debugMessenger) != VK_SUCCESS) {
			throw std::runtime_error("failed to set up debug messenger!");
		}
		//[6]or
		//  createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
		//[6]指定希望调用回调严重性类型
		//createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
		//	VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
		//	VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
		//[6]滤回调通知的消息类型
		//createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | 
		//	VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
		//	VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
		//[6]指定指向回调函数的指针
		//createInfo.pfnUserCallback = debugCallback;
		//[6]返回的回调函数
		//createInfo.pUserData = nullptr; 


	}
	//[6]创建代理函数
	VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const
		VkDebugUtilsMessengerCreateInfoEXT * pCreateInfo, const
		VkAllocationCallbacks * pAllocator, VkDebugUtilsMessengerEXT *
		pDebugMessenger) {
		auto func = (PFN_vkCreateDebugUtilsMessengerEXT)
			//[6]如果无法加载,函数将返回nullptr。
			vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");
		if (func != nullptr) {
			return func(instance, pCreateInfo, pAllocator, pDebugMessenger);
		}
		else {
			return VK_ERROR_EXTENSION_NOT_PRESENT;
		}
	}
	//[6]创建代理函数 销毁CreateDebugUtilsMessengerEXT
	void DestroyDebugUtilsMessengerEXT(VkInstance instance,
		VkDebugUtilsMessengerEXT debugMessenger, const
		VkAllocationCallbacks* pAllocator) {
		auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)
			vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
		if (func != nullptr) {
			func(instance, debugMessenger, pAllocator);
		}
	}

	//[6]仔细阅读扩展文档,就会发现有一种方法可以专门为这两个函数调用创建单独的debug utils messenger。
	//[6]它要求您只需在VkInstanceCreateInfo的pNext扩展字段中
	//[6]传递一个指向VkDebugUtilsMessengerCreateInfoEXT结构的指针。
	//[6]首先将messenger创建信息的填充提取到单独的函数中:
	void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT&
			createInfo) {
		createInfo = {};
		createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
		createInfo.messageSeverity =
			VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
			VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
			VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
		createInfo.messageType =
			VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
			VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
			VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
		createInfo.pfnUserCallback = debugCallback;
	}

private:
	GLFWwindow* window = nullptr;
	VkInstance instance = nullptr;
	VkDebugUtilsMessengerEXT debugMessenger;
};


int main() {
	HelloTriangleApplication app;
	//[1]捕获异常
	try {
		app.run();
	}
	catch (const std::exception& e) {
		std::cerr << e.what() << std::endl;
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
realesrgan-ncnn-vulkan-20211212-windows是一个基于ncnn框架和Vulkan图形API开发的图像超分辨率增强模型。它是由GitHub用户realsrgan开发的最新版本,最新发布日期为2021年12月12日,专为Windows操作系统而设计。 该模型的主要应用是图像超分辨率增强,通过提高图像的分辨率和细节,使图像看起来更加清晰和真实。它采用深度学习和卷积神经网络等先进的技术,能够将低分辨率的图像转换成高分辨率的图像,从而提升图像的质量和视觉效果。 realesrgan-ncnn-vulkan-20211212-windows的开发使用了ncnn框架和Vulkan图形API,这使得它能够在Windows系统上实现快速且高效的图像处理。ncnn是一个轻量级的深度学习框架,专注于在移动平台和嵌入式设备上实现高性能和低延迟的推理。而Vulkan图形API是一种跨平台的图形渲染和计算API,可以充分利用计算设备的性能,提供高效的图像处理和渲染能力。 realesrgan-ncnn-vulkan-20211212-windows的使用可以通过命令行或者图形界面进行,用户可以根据自己的需求和偏好选择适合的方式。该模型提供了训练好的权重参数,用户可以直接加载这些参数并进行图像超分辨率增强。此外,该模型还支持批量处理和视频处理,方便用户对多个图像进行处理。 总之,realesrgan-ncnn-vulkan-20211212-windows是一个高效、快速且易于使用的图像超分辨率增强模型,适用于Windows系统,并利用了ncnn框架和Vulkan图形API的优势,为用户提供了出色的图像处理效果。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值