Vulkan Tutorial 1 实例和物理设备

news2025/1/11 15:09:40

目录

0 基本代码

1 Instance

2 验证层

3 物理设备和队列系列

4 逻辑设备和队列


0 基本代码

首先包括LunarG SDK的Vulkan头,它提供了函数、结构和枚举。stdexcept'和iostream’头文件被包括在内,用于报告和传播错误

  1. 函数将被initVulkan函数调用
  2. 进入主循环,开始渲染帧mainLoop
  3. 一旦窗口关闭,mainLoop返回,取消分配在cleanup函数中使用的资源
  4. 如果在执行过程中发生任何致命的错误,将抛出一个std::runtime_error异常
#include <vulkan/vulkan.h>

#include <iostream>
#include <stdexcept>
#include <cstdlib>

class HelloTriangleApplication {
public:
    void run() {
        initVulkan();
        mainLoop();
        cleanup();
    }

private:
    void initVulkan() {

    }

    void mainLoop() {

    }

    void cleanup() {

    }
};

int main() {
    HelloTriangleApplication app;

    try {
        app.run();
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

之后的每一节都会增加一个新的函数,该函数将从initVulkan中调用,并在cleanup中为需要在最后释放的私有类成员增加一个或多个新的Vulkan对象。-显式销毁

Vulkan对象要么用vkCreateXXX这样的函数直接创建,要么通过vkAllocateXXX这样的函数分配给另一个对象。在确保一个对象不再被用于任何地方后,你需要用对应的vkDestroyXXX和vkFreeXXX销毁它。

pAllocator:这是一个可选的参数,允许你指定自定义内存分配器的回调。

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

void run() 
{
    initWindow();
    initVulkan();
    mainLoop();
    cleanup();
}

void initWindow()
{
    glfwInit();
//初始化GLFW库。
//GLFW最初被设计为创建一个OpenGL上下文,告诉它不要使用下面的调用创建OpenGL上下文
    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
    glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
//初始化该窗口
    window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
}

void mainLoop() 
{
    while (!glfwWindowShouldClose(window)) {
        glfwPollEvents();
    }
}

void cleanup()
{
    glfwDestroyWindow(window);

    glfwTerminate();
}

GLFW将包括它自己的定义并自动加载Vulkan头。 为了保持应用程序的运行,直到错误发生或窗口关闭,我们需要在mainLoop函数中添加一个事件循环,它循环并检查事件,如按下X按钮,直到窗口被用户关闭。一旦窗口被关闭,我们需要通过销毁它和终止GLFW本身来清理资源。

1 Instance

Vulkan API使用vkInstance对象来存储所有每个应用的状态。应用程序必须在执行任何其他Vulkan操作之前创建一个Vulkan实例,基本的Vulkan架构看起来是这样的:
 

创建一个instance来初始化Vulkan库,实例是你的应用程序和Vulkan库之间的连接,创建它需要向驱动指定一些关于你的应用程序的细节。

void initVulkan() 
{
    createInstance();
}

void createInstance() {
//填写关于我们应用程序的一些信息
    VkApplicationInfo appInfo{};
//sType成员中明确指定类型
    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;

//我们要使用哪些全局扩展和验证层
    VkInstanceCreateInfo createInfo{};
    createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
    createInfo.pApplicationInfo = &appInfo;

//Vulkan是一个与平台无关的API,这意味着你需要一个扩展来与窗口系统对接。
//GLFW有一个方便的内置函数,可以返回它所需要的扩展,我们可以将其传递给结构.
uint32_t glfwExtensionCount = 0;
const char** glfwExtensions;

glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

createInfo.enabledExtensionCount = glfwExtensionCount;
createInfo.ppEnabledExtensionNames = glfwExtensions;


//启用全局验证层
    createInfo.enabledLayerCount = 0;

//已经指定了Vulkan创建实例所需要的一切,我们最终可以执行vkCreateInstance调用:
if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
//几乎所有的Vulkan函数都会返回一个VkResult类型的值,这个值要么是VK_SUCCESS
//要么是一个错误代码
    throw std::runtime_error("failed to create instance!");
}

}

Vulkan中对象创建函数参数遵循的一般模式是。

  • 指向带有创建信息的结构的指针
  • 指向自定义分配器回调的指针,在本教程中总是nullptr
  • 指向存储新对象句柄的变量的指针

为了在创建实例之前检索支持的扩展列表,有一个vkEnumerateInstanceExtensionProperties函数。它需要一个存储扩展数量的变量指针和一个VkExtensionProperties的数组来存储扩展的细节。它还需要一个可选的第一个参数,允许我们通过一个特定的验证层来过滤扩展,我们现在将忽略这个参数。

//首先需要知道有多少个
uint32_t extensionCount = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);

//分配一个数组来保存扩展的细节
std::vector<VkExtensionProperties> extensions(extensionCount);

//查询扩展的详细信息
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());

//每个VkExtensionProperties结构包含一个扩展的名称和版本
std::cout << "available extensions:\n";

for (const auto& extension : extensions) {
    std::cout << '\t' << extension.extensionName << '\n';
}

 VkInstance应该只在程序退出前销毁。它可以在cleanup中用vkDestroyInstance函数销毁。vkDestroyInstance函数的参数是直接的。

2 验证层

Vulkan要求你对你所做的一切都要非常明确

Vulkan为此引入了一个被称为验证层的优雅系统。验证层是可选的组件,它与Vulkan函数调用挂钩以应用额外的操作。验证层中的常见操作是。

  • 对照规范检查参数值,以发现误用情况
  • 跟踪对象的创建和销毁以发现资源泄漏
  • 通过跟踪调用来源的线程来检查线程的安全性
  • 将每个调用及其参数记录到标准输出中
  • 跟踪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);
}

验证层可以自由堆叠, Vulkan没有内置任何验证层,但LunarG Vulkan SDK提供了一套不错的验证层,可以检查常见的错误。只有当验证层被安装到系统上时,才能使用它们。

  1. 首先在程序中添加两个配置变量,以指定要启用的层和是否启用它们。
  2. 添加一个新的函数checkValidationLayerSupport来检查所有请求的图层是否可用
  3. 检查validationLayers中的所有图层是否存在于availableLayers列表中
  4. 最后,修改VkInstanceCreateInfo结构实例,以包括验证层名称(如果它们被启用)
const uint32_t WIDTH = 800;
const uint32_t HEIGHT = 600;

const std::vector<const char*> validationLayers = {
    "VK_LAYER_KHRONOS_validation"
};

#ifdef NDEBUG
//NDEBUG宏是C++标准的一部分,意味着 “非调试”。
    const bool enableValidationLayers = false;
#else
    const bool enableValidationLayers = true;
#endif


bool checkValidationLayerSupport() {
    uint32_t layerCount;
    vkEnumerateInstanceLayerProperties(&layerCount, nullptr);

    std::vector<VkLayerProperties> availableLayers(layerCount);
    vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());

//检查validationLayers中的所有图层是否存在于availableLayers列表中
    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;
}

 消息回调使用VK_EXT_debug_utils扩展来设置一个带有回调的调试信使。

  1. 首先创建一个getRequiredExtensions函数,它将根据是否启用验证层来返回所需的扩展列表
std::vector<const char*> getRequiredExtensions() {
    uint32_t glfwExtensionCount = 0;
    const char** glfwExtensions;
    glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

    std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);

    if (enableValidationLayers) {
        extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
//使用了VK_EXT_DEBUG_UTILS_EXTENSION_NAME宏,它等于字面字符串 VK_EXT_debug_utils
    }

    return extensions;
}

现在让我们看看调试回调函数是什么样子的。用PFN_vkDebugUtilsMessengerCallbackEXT的原型添加一个新的静态成员函数,叫做debugCallbackVKAPI_ATTRVKAPI_CALL确保该函数具有正确的签名,以便Vulkan调用它。

static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
//pMessage: 调试信息是一个空尾的字符串
//pObjects: 与该消息相关的Vulkan对象句柄的数组
//objectCount: 数组中对象的数量
//最后,pUserData参数包含一个在设置回调时指定的指针,允许你向它传递你自己的数据。
    VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
    VkDebugUtilsMessageTypeFlagsEXT messageType,
    const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
    void* pUserData) {

    std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl;

    return VK_FALSE;//回调返回一个布尔值,表明触发验证层消息的Vulkan调用是否应该被终止。
}
//true,那么该调用将被中止

第一个参数指定消息的严重性,它是以下标志之一。

  • vk_debug_utils_message_severity_verbose_bit_ext: 诊断消息
  • vk_debug_utils_message_severity_info_bit_ext: 像创建资源的信息消息
  • vk_debug_utils_message_severity_warning_bit_ext: 关于不一定是错误的行为的消息,但很可能是您的应用程序中的一个错误。
  • vk_debug_utils_message_severity_error_bit_ext: 关于无效行为的信息,可能导致崩溃

这个枚举的值是这样设置的,你可以使用比较操作来检查一个消息与某个严重程度相比是否相等或更坏,例如:

if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
    // Message is important enough to show
}

我们需要在一个结构中填写关于信使及其回调的细节:这个结构应该被传递给vkCreateDebugUtilsMessengerEXT函数来创建VkDebugUtilsMessengerEXT对象。不幸的是,由于这个函数是一个扩展函数,它不会被自动加载。我们必须自己使用vkGetInstanceProcAddr来查找它的地址。

VkDebugUtilsMessengerCreateInfoEXT 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;
//回调函数的指针。你可以选择传递一个指向pUserData字段的指针
createInfo.pfnUserCallback = debugCallback;
createInfo.pUserData = nullptr; // Optional

//这个结构应该被传递给vkCreateDebugUtilsMessengerEXT函数来创建VkDebugUtilsMessengerEXT对象
VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) {
    auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");
    if (func != nullptr) {
        return func(instance, pCreateInfo, pAllocator, pDebugMessenger);
    } else {
        return VK_ERROR_EXTENSION_NOT_PRESENT;
    }
}

VkDebugUtilsMessengerEXT对象也需要通过调用vkDestroyDebugUtilsMessengerEXT来清理。与vkCreateDebugUtilsMessengerEXT类似,该函数需要明确加载。在CreateDebugUtilsMessengerEXT下面创建另一个代理函数。

void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) {
    auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
    if (func != nullptr) {
        func(instance, debugMessenger, pAllocator);
    }
}

现在让我们故意犯一个错误,看看验证层的作用。暂时删除cleanup函数中对DestroyDebugUtilsMessengerEXT的调用,然后运行你的程序。一旦它退出,你应该看到类似这样的东西。 

3 物理设备和队列系列

在通过VkInstance初始化Vulkan库后,我们需要在系统中寻找并选择一个支持我们所需功能的显卡。事实上,我们可以选择任何数量的显卡并同时使用它们,但在本教程中,我们将坚持使用第一个适合我们需求的显卡。

void initVulkan() {
    createInstance();
    setupDebugMessenger();
    pickPhysicalDevice();
}
//我们最终选择的显卡将被存储在一个VkPhysicalDevice句柄中,它被作为一个新的类成员添加
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;



void pickPhysicalDevice() {
//开始时只查询数字
    uint32_t deviceCount = 0;
    vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
//如果有0台设备支持Vulkan,那么就没有必要再继续下去了
    if (deviceCount == 0) {
        throw std::runtime_error("failed to find GPUs with Vulkan support!");
    }

//分配一个数组来保存所有的VkPhysicalDevice句柄
    std::vector<VkPhysicalDevice> devices(deviceCount);
    vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());

    for (const auto& device : devices) 
    {
        if (isDeviceSuitable(device)) 
        {
            physicalDevice = device;
            break;
        }
    }

    if (physicalDevice == VK_NULL_HANDLE)
    {
        throw std::runtime_error("failed to find a suitable GPU!");
    }
}

//每一个进行评估,并检查它们是否适合于我们想要执行的操作
bool isDeviceSuitable(VkPhysicalDevice device) {
    return true;
}

为了评估一个设备的适用性,我们可以从查询一些细节开始。基本的设备属性,如名称、类型和支持的Vulkan版本,可以使用vkGetPhysicalDeviceProperties进行查询。

VkPhysicalDeviceProperties deviceProperties;
vkGetPhysicalDeviceProperties(device, &deviceProperties);

纹理压缩、64位浮点和多视口渲染(对VR有用)等可选特性的支持可以用vkGetPhysicalDeviceFeatures查询:

VkPhysicalDeviceFeatures deviceFeatures;
vkGetPhysicalDeviceFeatures(device, &deviceFeatures);

假设我们认为我们的应用程序只适用于支持几何着色器的专用图形卡。那么 isDeviceSuitable函数会是这样的:

bool isDeviceSuitable(VkPhysicalDevice device) {
    VkPhysicalDeviceProperties deviceProperties;
    VkPhysicalDeviceFeatures deviceFeatures;
    vkGetPhysicalDeviceProperties(device, &deviceProperties);
    vkGetPhysicalDeviceFeatures(device, &deviceFeatures);

    return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU &&
           deviceFeatures.geometryShader;
}

Queue families

之前已经简单地提到过,Vulkan中几乎所有的操作,从绘图到上传纹理,都需要将命令提交给一个队列。有不同类型的队列,它们来自不同的队列家族,每个队列家族只允许一个子集的命令。例如,可能有一个队列家族只允许处理计算命令,或者一个只允许内存传输相关的命令。

现在我们只想寻找支持图形命令的队列,所以这个函数可以是这样的:

struct QueueFamilyIndices {
//std::optional是一个包装器,在你给它赋值之前不包含任何值。
//在任何时候,你都可以通过调用其has_value()成员函数来查询它是否包含一个值
    std::optional<uint32_t> graphicsFamily;

    bool isComplete() {
        return graphicsFamily.has_value();
    }
    
};

//但是如果一个队列家族不可用呢?我们可以在findQueueFamilies中抛出一个异常
QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
    QueueFamilyIndices indices;
    // Assign index to queue families that could be found
    uint32_t queueFamilyCount = 0;
    vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);

    std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
    vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());

    
    int i = 0;
    for (const auto& queueFamily : queueFamilies) 
    {
        if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) 
        {
            indices.graphicsFamily = i;
        }

        i++;
    }

    return indices;
}

//在isDeviceSuitable函数中使用它作为检查
bool isDeviceSuitable(VkPhysicalDevice device) {
    QueueFamilyIndices indices = findQueueFamilies(device);

    return indices.isComplete();
}

4 逻辑设备和队列

 

 Vulkan 逻辑设备与队列,在选择要使用的物理设备之后,我们需要设置一个逻辑设备用于交互。逻辑设备创建过程与instance创建过程类似,也需要描述我们需要使用的功能。因为我们已经查询过哪些队列簇可用,在这里需要进一步为逻辑设备创建具体类型的命令队列。如果有不同的需求,也可以基于同一个物理设备创建多个逻辑设备。

VkDevice device;//存储逻辑设备句柄。

 添加一个createLogicalDevice函数,从initVulkan中调用。

void createLogicalDevice() 
{
    //要的单个队列家族的队列数量
    QueueFamilyIndices indices = findQueueFamilies(physicalDevice);

    VkDeviceQueueCreateInfo queueCreateInfo{};
    queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
    queueCreateInfo.queueFamilyIndex = indices.graphicsFamily.value();
    queueCreateInfo.queueCount = 1;

    //Vulkan让你为队列分配优先级,以影响命令缓冲区执行的调度
    float queuePriority = 1.0f;
    queueCreateInfo.pQueuePriorities = &queuePriority;
    //查询支持的特性
    VkPhysicalDeviceFeatures deviceFeatures{};

    //首先添加指向队列创建信息和设备特征结构的指针
    createInfo.pQueueCreateInfos = &queueCreateInfo;
    createInfo.queueCreateInfoCount = 1;

//填写主VkDeviceCreateInfo结构
    VkDeviceCreateInfo createInfo{};
    createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;

   createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
   createInfo.pQueueCreateInfos = queueCreateInfos.data();

   createInfo.pEnabledFeatures = &deviceFeatures;

   createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
   createInfo.ppEnabledExtensionNames = deviceExtensions.data();

   if (enableValidationLayers) {
         createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
            createInfo.ppEnabledLayerNames = validationLayers.data();
   }
   else {
            createInfo.enabledLayerCount = 0;
   }
//实例化这个逻辑设备
   if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
            throw std::runtime_error("failed to create logical device!");
   }
//存储图形队列的句柄
//vkGetDeviceQueue函数来检索每个队列家族的队列柄

        vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
        vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/561450.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言生成随机数

目录 概念&#xff1a; 具体运用 实战 1.只使用rand()函数 2.使用rand()函数和srand() 函数 概念&#xff1a; 在实践中&#xff0c;找到真正的随机数字是困难的。数字计算机只能在⼀个限定的范围内和有限的精度下去处理数字。在⼤多数情况下,最好的⽅法是产⽣伪随机数&am…

【AI 孙燕姿】歌声转换技术原理浅析

最近打开B站&#xff0c;首页会推荐很多以【AI 孙燕姿】开头的视频&#xff0c;内容是用孙燕姿的音色去唱其他歌手的歌。出于好(ceng)奇(re)心(du)&#xff0c;作者去了解下歌声转换&#xff08;Singing Voice Conversion&#xff0c;SVC&#xff09;这个任务。不看不知道&…

比亲妈都细,从0-1手把手教你搭建Elasticsearch+kibana+IK分词器

前言&#xff1a; 最近用到了ElasticsearchkibanaIK分词器&#xff0c;但是基本上能搜到的所有教程、视频都用的是老版本的&#xff0c;奈何我又空有一身反骨&#xff0c;我就不爱用老版本的&#xff0c;结果就一个一个的踩坑。 Elasticsearch是我用过的最坑的软件之一了&#…

通过ChatGPT跟MetaHuman对话,Android/iOS兼容

一、申请ChatGPT的API-KEY 1.通过 openAI官网申请API-KEY 2.参考使用腾讯云函数一分钟搭建 OpenAI 免翻墙代理搭建openAI免翻墙代理 3.通过Postman测试一下openAI函数是否可被调用,传入BearerToken和Body参数,ChatGPT即可返回应答数据 二、启用必要的插件 1.启用文字转语…

5.24黄金短线上涨能否继续做空?今日如何布局

近期有哪些消息面影响黄金走势&#xff1f;今日黄金多空该如何研判&#xff1f; ​黄金消息面解析&#xff1a;黄金消息面解析&#xff1a;周二(5月23日)&#xff0c;国际金价延续隔夜跌势&#xff0c;因在美联储官员的鹰派言论推动下&#xff0c;美元重启涨势&#xff0c;尽管…

chatgpt赋能Python-python_padding

Python中Padding的介绍 在Python编程中&#xff0c;Padding是一种在字符串或序列前后添加空格或其他占位符来实现对齐的技术。这种技术常用于数据格式化、打印输出、加密等场合&#xff0c;具有很高的实用性。 Python中Padding技术主要由三种函数实现&#xff1a;rjust, ljus…

当系统部署到测试环境,或线上时,该如何查看当前FastJson的版本

当系统部署到测试环境&#xff0c;或线上时&#xff0c;该如何查看当前FastJson的版本 提示&#xff1a;看见网上很多方法&#xff0c;有的不是很实用了&#xff0c;近日自己将fastJson版本升级到了1.2.76&#xff1b;需要在系统中查看版本信息&#xff1b;用到了如下方法&…

Java——《面试题——JVM篇》

前文 java——《面试题——基础篇》CSDN博客 1.知识点汇总 JVM是Java运行基础,面试时一定会遇到JVM的有关问题,内容相对集中,但对只是深度要求较高. 其中内存模型&#xff0c;类加载机制&#xff0c;GC是重点方面.性能调优部分更偏向应用&#xff0c;重点突出实践能力。编译…

网络安全技能差距的高成本

犯罪分子总是利用我们的无能为他们谋取利益。根据&#xff08;ISC&#xff09;的一份报告&#xff0c;全球网络安全专业人员短缺近 300 万。导致网络安全专家短缺的原因是多方面的&#xff0c;例如网络安全威胁的复杂性越来越高&#xff0c;技术进步的速度越来越快&#xff0c;…

C++继承技术

方法覆盖 virtual关键字 只有在基类中声明为 virtual 的方法才能被派生类正确覆盖。关键字位于方法声明的开头&#xff0c;如下面的 Base 的修改版本所示&#xff1a; class Base {public:virtual void someMethod() {}protected:int m_protectedInt { 0 };private:int m_pr…

一图看懂 dis 模块:将 python 字节码反汇编为助记符,资料整理+笔记(大全)

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 一图看懂 dis 模块&#xff1a;将 python 字节码反汇编为助记符&#xff0c;资料整理笔记&#xff08;大全&#xff09; &#x1f9ca;摘要&#x1f9ca;模块图&#x1f9ca;类关系图…

chatgpt赋能Python-python_plt_散点图

Python plt散点图&#xff1a;学习数据可视化的有力武器 Python是一种广泛使用的编程语言&#xff0c;广泛应用于数据科学&#xff0c;数据分析&#xff0c;计算机视觉等领域。而在数据可视化方面&#xff0c;Python也提供了很多强大的工具&#xff0c;其中plt散点图是一种非常…

Chatbot UI老外在用的gpt网页版 搭建方法分享!

新建了一个网站 https://ai.weoknow.com/ 每天给大家更新可用的国内可用chatGPT资源 Chatbot UI 高仿ChatGPT官网&#xff0c;中文还支持贼好&#xff0c;界面美观度间距还需要打磨。是老外做的吗&#xff1f; ​ 环境部署 更新环境 apt update -y && apt upg…

09 - 进程长参数编程

---- 整理自狄泰软件唐佐林老师课程 查看所有文章链接&#xff1a;&#xff08;更新中&#xff09;Linux系统编程训练营 - 目录 文章目录 1. 短选项扩展编程1.1 再论进程参数&#xff08;短选项&#xff09;1.2 进程短选项示例 2. 进程长参数编程2.1 进程长参数示例2.2 进程长参…

MATLAB NAN或INF无效点去除 (14)

MATLAB NAN或INF无效点去除 (14) 一、算法介绍二、算法实现1.代码(含注释说明)2.效果(无效点去除前后点坐标展示)一、算法介绍 仅就一般情况来说,激光点云受到测量影响,可能会产生无效点,即坐标值为NAN或者INF等,这种点会严重干扰一些几何特征,例如法向等的计算,因…

HummerRisk V1.1.0 发布

HummerRisk V1.1.0发布&#xff1a; 重构了新的Dashboard&#xff0c;新增报告中心&#xff0c;增加新的 Linux 主机系统的安全扫描、安全审计功能。增加部分Docker相关的主机检测&#xff0c;镜像仓库新增公有云阿里云、腾讯云类型&#xff0c;新增镜像分组管理等功能&#x…

chatgpt赋能Python-python_os拷贝文件

Python os拷贝文件 – 从简介到实现 Python os库是一个经常使用的工具&#xff0c;它是Python的标准库&#xff0c;提供了与操作系统进行交互的函数和方法。其中&#xff0c;os拷贝文件是其常用的功能之一&#xff0c;可以用来实现文件备份、文件复制等等操作。接下来&#xf…

pthread多线程: 线程泄漏的检测

文章目录 1. 目的2. 什么是线程泄漏3. pthread 线程泄漏例子3.1 代码3.2 编译和运行3.3 简要分析 4. 检测线程泄漏4.1 编译链接时传入参数 -fsanitizethread4.2 确认 TSAN_OPTIONS 环境变量 5. 修复线程泄漏5.1 方法1&#xff1a; 主线程等待子线程5.2 方法2&#xff1a;子线程…

k8s网络如何连接?

在k8s中网络连接可以分为 容器与容器: 所有在pod中的容器表现为在同一个host&#xff0c;他们之间可以通过端口进行连接 pod与pod: 因为每个pod都有一个ip&#xff0c;因此pod可以通过ip进行直接连接 在不同主机上pod究竟是如何连接的呢&#xff1f;毕竟pod ip只是虚拟的&…

Vue+Element-ui实现表格导出和导入

表格导出&#xff0c;填写数据&#xff0c;导入表格 需求&#xff1a;表格导出&#xff0c;填写数据&#xff0c;导入数据表格文件存储在前端表格文件不存储 需求&#xff1a;表格导出&#xff0c;填写数据&#xff0c;导入数据 分析需求&#xff1a; &#xff08;1&#xff0…