WebGPU(八):三角形渲染

news2025/1/11 10:03:11

WebGPU(八):三角形渲染

三角形的渲染其实很简单,只是需要设置很详细的render pipeline以及shader。

// Select which render pipeline to use
wgpuRenderPassEncoderSetPipeline(renderPass, pipeline);
// Draw 1 instance of a 3-vertices shape
wgpuRenderPassEncoderDraw(renderPass, 3, 1, 0, 0);

渲染管线(Render Pipeline)

为了实现高性能的实时 3D 渲染,GPU 通过预定义的管道处理图形。管道本身总是相同的(它通常被烧录到硬件的物理架构中),但我们可以通过多种方式对其进行配置。为此,WebGPU 提供了一个渲染管线Render Pipeline对象。

如果您熟悉 OpenGL,则可以将 WebGPU 的渲染管线视为配置渲染管线的所有有状态函数(stateful functions)的记忆值(memorized value).

下图说明了渲染管线执行的数据处理阶段的顺序。它们中的大多数是固定函数级,为此我们只能自定义一些变量,但最强大的是可编程级。
在这些可编程阶段,真正的程序(称为着色器)以大规模并行方式执行(跨输入顶点或跨栅格化片段)。
在这里插入图片描述

其他图形 API 提供对可编程性更强的阶段(geometry着色器、mesh着色器、task着色器)的访问。这些不是 Web 标准的一部分。它们将来可能会作为仅限本机的扩展提供,但在很多情况下,可以使用general compute shader来模拟它们的行为。

像以往一样,我们先创建pipeline的描述字符

C++版:
RenderPipelineDescriptor pipelineDesc;
// [...] Describe render pipeline
RenderPipeline pipeline = device.createRenderPipeline(pipelineDesc);

C版:
WGPURenderPipelineDescriptor pipelineDesc{};
pipelineDesc.nextInChain = nullptr;
// [...] Describe render pipeline
WGPURenderPipeline pipeline = wgpuDeviceCreateRenderPipeline(device, &pipelineDesc);

Vertex pipeline state(顶点管线状态)

vertex fetch以及vertex shader阶段的设置都是通过vertex state这个结构体来配置的,可以通过pipelineDesc.vertex来获取该字符。
在render pipeline 中,它hi先获取vertex的特性,包括位置,颜色,法线等,但是在我们这个历程中,我们固定三角形三个顶点的位置,因而我们完全不需要三角形的位置缓存。

pipelineDesc.vertex.bufferCount = 0;
pipelineDesc.vertex.buffers = nullptr;

Vertex Shader
Vertex shader一般包含下面几个组成:

  • shader module, 包含shader的源码
  • entry point,这是着色器模块中必须为每个顶点调用的函数的名称。这使给定的着色器模块能够同时包含多个渲染管线配置的入口点。在本例程中,我们对顶点和片段着色器使用相同的模块。
  • 着色器常量的值赋值数组。我们暂时不使用。
pipelineDesc.vertex.module = shaderModule;
pipelineDesc.vertex.entryPoint = "vs_main";
pipelineDesc.vertex.constantCount = 0;
pipelineDesc.vertex.constants = nullptr;

Primitive pipeline state(图元管线状态)

pipelineDesc.primitive中的primitive state结构体可以设置 primitive assembly以及rasterization阶段。

rasterization,光栅化是 GPU 实现的 3D 渲染算法的核心。它将基元(primitive)(点、线或三角形)转换为一系列片段(fragments),这些片段对应于基元覆盖的像素。它插入顶点着色器输出的任何额外属性,以便每个片段接收所有属性的值。
基元程序集配置包括说明之前获取的顶点数组必须如何连接到点云、线框或三角组。我们将其设置为默认值。

C++// Each sequence of 3 vertices is considered as a triangle
pipelineDesc.primitive.topology = PrimitiveTopology::TriangleList;

// We'll see later how to specify the order in which vertices should be
// connected. When not specified, vertices are considered sequentially.
pipelineDesc.primitive.stripIndexFormat = IndexFormat::Undefined;

// The face orientation is defined by assuming that when looking
// from the front of the face, its corner vertices are enumerated
// in the counter-clockwise (CCW) order.
pipelineDesc.primitive.frontFace = FrontFace::CCW;

// But the face orientation does not matter much because we do not
// cull (i.e. "hide") the faces pointing away from us (which is often
// used for optimization).
pipelineDesc.primitive.cullMode = CullMode::None;

C:
// Each sequence of 3 vertices is considered as a triangle
pipelineDesc.primitive.topology = WGPUPrimitiveTopology_TriangleList;

// We'll see later how to specify the order in which vertices should be
// connected. When not specified, vertices are considered sequentially.
pipelineDesc.primitive.stripIndexFormat = WGPUIndexFormat_Undefined;

// The face orientation is defined by assuming that when looking
// from the front of the face, its corner vertices are enumerated
// in the counter-clockwise (CCW) order.
pipelineDesc.primitive.frontFace = WGPUFrontFace_CCW;

// But the face orientation does not matter much because we do not
// cull (i.e. "hide") the faces pointing away from us (which is often
// used for optimization).
pipelineDesc.primitive.cullMode = WGPUCullMode_None;

通常我们设置剔除模式(cull MOde)以避免在渲染对象内部时浪费资源。但是对于初学者来说,几个小时在屏幕上什么也看不到只是发现三角形朝向错误的方向可能会非常令人沮丧,所以我们可以在开发时将其设置为None

Fragment shader

将基元(primitive)转换为片段(fragment)后,将为每个primitive调用片段着色器阶段。此着色器接收顶点着色器生成的插值,并且必须依次输出fragment的最终颜色。

请记住,所有这些阶段stages都发生在一个并行和异步的环境中。渲染大型网格时,可以在栅格化最后一个基元之前调用第一个基元的片段着色器。

fragment shader的设置非常类似vertex的设置:

C++:
FragmentState fragmentState;
fragmentState.module = shaderModule;
fragmentState.entryPoint = "fs_main";
fragmentState.constantCount = 0;
fragmentState.constants = nullptr;
// [...] We'll configure the blend stage here
pipelineDesc.fragment = &fragmentState;

C:
WGPUFragmentState fragmentState{};
fragmentState.module = shaderModule;
fragmentState.entryPoint = "fs_main";
fragmentState.constantCount = 0;
fragmentState.constants = nullptr;
// [...] We'll configure the blend stage here
pipelineDesc.fragment = &fragmentState;

请注意,片段阶段是可选的,其(可能为 null)指针也是可选的,而不是直接设置片段状态结构。

Stencil / Depth stage (模板/深度过程)

这两个概念在之前的attachment的内容中也提及。
深度测试Depth test用于丢弃与同一像素关联的其他片段后面的片段。请记住,片段是给定基元在给定像素上的投影,因此当基元相互重叠时,会为同一像素发射多个片段。片段具有深度信息,供深度测试使用。
模板测试Stencil test是另一种片段丢弃机制,用于隐藏基于先前渲染的基元的片段。让我们暂时忽略深度和模板机制,我们将在深度缓冲区章节中介绍它们。

pipelineDesc.depthStencil = nullptr;

Blending(融合过程)

混合阶段(blending stage)采用每个片段的颜色和”涂料“它到目标color attachment上。如果目标像素中的原始颜色是(rd,gd,bd,ad),而要绘制的源片段的颜色是(rs,gs,bs,as),那么最终像素的最终颜色(r,g,b,a)应该是什么?这是混合状态指定的内容。
我们还必须指定颜色要存储在最终attachment中的格式(即如何将值表示为零和一)。

C++:
BlendState blendState;
// [...] Configure blend state

ColorTargetState colorTarget;
colorTarget.format = swapChainFormat;
colorTarget.blend = &blendState;
colorTarget.writeMask = ColorWriteMask::All; // We could write to only some of the color channels.

// We have only one target because our render pass has only one output color
// attachment.
fragmentState.targetCount = 1;
fragmentState.targets = &colorTarget;

C:
WGPUBlendState blendState{};
// [...] Configure blend state

WGPUColorTargetState colorTarget{};
colorTarget.format = swapChainFormat;
colorTarget.blend = &blendState;
colorTarget.writeMask = WGPUColorWriteMask_All; // We could write to only some of the color channels.

// We have only one target because our render pass has only one output color
// attachment.
fragmentState.targetCount = 1;
fragmentState.targets = &colorTarget;

混合方程可以单独设置 rgb 通道和 alpha 通道,通常采用以下形式:
在这里插入图片描述

C++:
blendState.color.srcFactor = BlendFactor::SrcAlpha;
blendState.color.dstFactor = BlendFactor::OneMinusSrcAlpha;
blendState.color.operation = BlendOperation::Add;

C:
blendState.color.srcFactor = WGPUBlendFactor_SrcAlpha;
blendState.color.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha;
blendState.color.operation = WGPUBlendOperation_Add;

对于alpha通道:
在这里插入图片描述

多重采样(Multi-sampling)

前面说过,片段(fragment)是投影到特定像素上的基元(primitive)部分。实际上,我们可以将像素拆分为子元素(sub-element),称为样本(sampling),片段与样本相关联。像素的值是通过对其关联样本求平均值来计算的。
这种机制称为多重采样,用于抗锯齿(anti-aliasing),但我们暂时将其关闭,方法是将每个像素的采样数设置为 1。

// Samples per pixel
pipelineDesc.multisample.count = 1;
// Default value for the mask, meaning "all bits on"
pipelineDesc.multisample.mask = ~0u;
// Default value as well (irrelevant for count = 1 anyways)
pipelineDesc.multisample.alphaToCoverageEnabled = false;

Shaders(着色器)

点和片段可编程阶段都使用相同的着色器模块,我们必须首先创建该模块。这个模块是一种动态库,就像.dll、.so 或 .dylib 文件一样,只不过它使用你的 GPU 的二进制语言而不是你的 CPU 的二进制语言。
像任何编译的程序一样,着色器首先用人类可写的编程语言编写,然后编译成低级机器代码。但是,低级代码高度依赖于硬件,并且通常不会公开记录。因此,应用程序与着色器的源代码一起分发,这些源代码是在初始化应用程序时动态编译(compiled on the fly)的。

shader code

WebGPU 正式使用的着色器语言称为 WGSL,即 WebGPU 着色语言(WebGPU Shading Language)。任何 WebGPU 的实现都必须支持它,并且 JavaScript 版本的 API 仅支持 WGSL,但native header还提供了提供用 SPIR-V 或 GLSL 编写的着色器的可能性(仅wgpu-native支持)。

SPIR-V更像是一种中间表示,一个字节码,而不是人们手动编写的东西。它是 Vulkan API 使用的着色器语言,并且存在许多工具来交叉编译来自其他常见着色器语言(HLSL、GLSL)的代码,因此当需要重用现有的着色器代码库时,这是一个较好的选择。
另请注意,WGSL 被设计为SPIR-V 编程模型的人类可编辑版本,因此从 SPIR-V 到 WGSL 的转换是高效且无损的(为此使用 Naga 或 Tint)

三角形的绘制shader如下:

@vertex
fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4f {
    var p = vec2f(0.0, 0.0);
    if (in_vertex_index == 0u) {
        p = vec2f(-0.5, -0.5);
    } else if (in_vertex_index == 1u) {
        p = vec2f(0.5, -0.5);
    } else {
        p = vec2f(0.0, 0.5);
    }
    return vec4f(p, 0.0, 1.0);
}

@fragment
fn fs_main() -> @location(0) vec4f {
    return vec4f(0.0, 0.4, 1.0, 1.0);
}

这里builtin(vertex_index)的数值如何确定?思考:应该是默认为0所以后面自动进行初始化
@开头的标记称为属性Attribute,并用各种信息装饰后面的对象。例如, @builtin(vertex_index)告诉参数in_vertex_index 这个参数可以用其他任何名称,该参数将由内置输入顶点属性(即顶点索引)填充。我们使用它来设置着色器的输出值,该值被标记为必须由光栅器解释为顶点位置的内容。@@builtin(vertex_index)in_vertex_index@builtin(position)
我们可以保存到文本中,但同样可以作为字符保存到代码中:

const char* shaderSource = R"(
[...] The WGSL shader source code
)";

创建shader模块

像之前一样,我们先创建shader的描述字符:

C++:
ShaderModuleDescriptor shaderDesc;
// [...] Describe shader module
ShaderModule shaderModule = device.createShaderModule(shaderDesc);
C:
WGPUShaderModuleDescriptor shaderDesc{};
// [...] Describe shader module
WGPUShaderModule shaderModule = wgpuDeviceCreateShaderModule(device, &shaderDesc);

乍一看,这个描述符似乎只有一组编译“提示”需要填充,我们将其留空(使用 Dawn 时甚至根本没有):

#ifdef WEBGPU_BACKEND_WGPU
shaderDesc.hintCount = 0;
shaderDesc.hints = nullptr;
#endif

nextInChain 指针是 WebGPU 扩展机制的入口点。它要么为 null,要么指向WGPUChainedStruct类型的结构。这个结构非常简单。首先,它可以递归地有一个元素(同样,null或指向某些WGPUChainedStruct类型的结构)。其次,它有一个结构类型sType,这是一个枚举,告诉链元素可以在哪个结构中铸造。定义以WGPUChainedStruct chain字段开头的每个结构都有一个关联的 SType。

要从 WGSL 代码创建着色器模块,我们使用ShaderModuleWGSLDescriptor SType。SPIR-V 着色器同样可以使用WGPUShaderModuleSPIRVDescriptor标识符创建

shaderCodeDesc.chain转换为简单WGPUChainedStruct时,该字段对应于链接结构,必须设置为相应的 SType 枚举值

C++:
ShaderModuleWGSLDescriptor shaderCodeDesc;
// Set the chained struct's header
shaderCodeDesc.chain.next = nullptr;
shaderCodeDesc.chain.sType = SType::ShaderModuleWGSLDescriptor;
// Connect the chain
shaderDesc.nextInChain = &shaderCodeDesc.chain;

C:
WGPUShaderModuleWGSLDescriptor shaderCodeDesc{};
// Set the chained struct's header
shaderCodeDesc.chain.next = nullptr;
shaderCodeDesc.chain.sType = WGPUSType_ShaderModuleWGSLDescriptor;
// Connect the chain
shaderDesc.nextInChain = &shaderCodeDesc.chain;

最后我们填充shader源码到描述符中:

shaderCodeDesc.code = shaderSource;

Pipeline layout

在我们运行代码之前的最后一件事:着色器可能需要访问输入和输出资源(缓冲区和/或纹理)。通过配置内存布局,这些资源可供管道使用。我们的第一个示例不使用任何资源.

pipelineDesc.layout = nullptr;

实际上,将管道布局设置为 并不意味着没有输入/输出资源。相反,它要求后端通过检查着色器(在这种情况下是等效的)来自己找出布局.

测试代码及其结果

/**
 * This file is part of the "Learn WebGPU for C++" book.
 *   https://eliemichel.github.io/LearnWebGPU
 * 
 * MIT License
 * Copyright (c) 2022-2023 Elie Michel
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

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

#define WEBGPU_CPP_IMPLEMENTATION
#include <webgpu/webgpu.hpp>

#include <iostream>
#include <cassert>

using namespace wgpu;

int main (int, char**) {
	Instance instance = createInstance(InstanceDescriptor{});
	if (!instance) {
		std::cerr << "Could not initialize WebGPU!" << std::endl;
		return 1;
	}

	if (!glfwInit()) {
		std::cerr << "Could not initialize GLFW!" << std::endl;
		return 1;
	}

	glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
	glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
	GLFWwindow* window = glfwCreateWindow(640, 480, "Learn WebGPU", NULL, NULL);
	if (!window) {
		std::cerr << "Could not open window!" << std::endl;
		return 1;
	}

	std::cout << "Requesting adapter..." << std::endl;
	Surface surface = glfwGetWGPUSurface(instance, window);
	RequestAdapterOptions adapterOpts;
	adapterOpts.compatibleSurface = surface;
	Adapter adapter = instance.requestAdapter(adapterOpts);
	std::cout << "Got adapter: " << adapter << std::endl;

	std::cout << "Requesting device..." << std::endl;
	DeviceDescriptor deviceDesc;
	deviceDesc.label = "My Device";
	deviceDesc.requiredFeaturesCount = 0;
	deviceDesc.requiredLimits = nullptr;
	deviceDesc.defaultQueue.label = "The default queue";
	Device device = adapter.requestDevice(deviceDesc);
	std::cout << "Got device: " << device << std::endl;

	// Add an error callback for more debug info
	auto h = device.setUncapturedErrorCallback([](ErrorType type, char const* message) {
		std::cout << "Device error: type " << type;
		if (message) std::cout << " (message: " << message << ")";
		std::cout << std::endl;
	});

	Queue queue = device.getQueue();

	std::cout << "Creating swapchain..." << std::endl;
#ifdef WEBGPU_BACKEND_WGPU
	TextureFormat swapChainFormat = surface.getPreferredFormat(adapter);
#else
	TextureFormat swapChainFormat = TextureFormat::BGRA8Unorm;
#endif
	SwapChainDescriptor swapChainDesc;
	swapChainDesc.width = 640;
	swapChainDesc.height = 480;
	swapChainDesc.usage = TextureUsage::RenderAttachment;
	swapChainDesc.format = swapChainFormat;
	swapChainDesc.presentMode = PresentMode::Fifo;
	SwapChain swapChain = device.createSwapChain(surface, swapChainDesc);
	std::cout << "Swapchain: " << swapChain << std::endl;

	std::cout << "Creating shader module..." << std::endl;
	const char* shaderSource = R"(
@vertex
fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4<f32> {
	var p = vec2f(0.0, 0.0);
	if (in_vertex_index == 0u) {
		p = vec2f(-0.5, -0.5);
	} else if (in_vertex_index == 1u) {
		p = vec2f(0.5, -0.5);
	} else {
		p = vec2f(0.0, 0.5);
	}
	return vec4f(p, 0.0, 1.0);
}

@fragment
fn fs_main() -> @location(0) vec4f {
    return vec4f(0.0, 0.4, 1.0, 1.0);
}
)";

	ShaderModuleDescriptor shaderDesc;
#ifdef WEBGPU_BACKEND_WGPU
	shaderDesc.hintCount = 0;
	shaderDesc.hints = nullptr;
#endif

	// Use the extension mechanism to load a WGSL shader source code
	ShaderModuleWGSLDescriptor shaderCodeDesc;
	// Set the chained struct's header
	shaderCodeDesc.chain.next = nullptr;
	shaderCodeDesc.chain.sType = SType::ShaderModuleWGSLDescriptor;
	// Connect the chain
	shaderDesc.nextInChain = &shaderCodeDesc.chain;

	// Setup the actual payload of the shader code descriptor
	shaderCodeDesc.code = shaderSource;

	ShaderModule shaderModule = device.createShaderModule(shaderDesc);
	std::cout << "Shader module: " << shaderModule << std::endl;

	std::cout << "Creating render pipeline..." << std::endl;
	RenderPipelineDescriptor pipelineDesc;

	// Vertex fetch
	// (We don't use any input buffer so far)
	pipelineDesc.vertex.bufferCount = 0;
	pipelineDesc.vertex.buffers = nullptr;

	// Vertex shader
	pipelineDesc.vertex.module = shaderModule;
	pipelineDesc.vertex.entryPoint = "vs_main";
	pipelineDesc.vertex.constantCount = 0;
	pipelineDesc.vertex.constants = nullptr;

	// Primitive assembly and rasterization
	// Each sequence of 3 vertices is considered as a triangle
	pipelineDesc.primitive.topology = PrimitiveTopology::TriangleList;
	// We'll see later how to specify the order in which vertices should be
	// connected. When not specified, vertices are considered sequentially.
	pipelineDesc.primitive.stripIndexFormat = IndexFormat::Undefined;
	// The face orientation is defined by assuming that when looking
	// from the front of the face, its corner vertices are enumerated
	// in the counter-clockwise (CCW) order.
	pipelineDesc.primitive.frontFace = FrontFace::CCW;
	// But the face orientation does not matter much because we do not
	// cull (i.e. "hide") the faces pointing away from us (which is often
	// used for optimization).
	pipelineDesc.primitive.cullMode = CullMode::None;

	// Fragment shader
	FragmentState fragmentState;
	pipelineDesc.fragment = &fragmentState;
	fragmentState.module = shaderModule;
	fragmentState.entryPoint = "fs_main";
	fragmentState.constantCount = 0;
	fragmentState.constants = nullptr;

	// Configure blend state
	BlendState blendState;
	// Usual alpha blending for the color:
	blendState.color.srcFactor = BlendFactor::SrcAlpha;
	blendState.color.dstFactor = BlendFactor::OneMinusSrcAlpha;
	blendState.color.operation = BlendOperation::Add;
	// We leave the target alpha untouched:
	blendState.alpha.srcFactor = BlendFactor::Zero;
	blendState.alpha.dstFactor = BlendFactor::One;
	blendState.alpha.operation = BlendOperation::Add;

	ColorTargetState colorTarget;
	colorTarget.format = swapChainFormat;
	colorTarget.blend = &blendState;
	colorTarget.writeMask = ColorWriteMask::All; // We could write to only some of the color channels.

	// We have only one target because our render pass has only one output color
	// attachment.
	fragmentState.targetCount = 1;
	fragmentState.targets = &colorTarget;
	
	// Depth and stencil tests are not used here
	pipelineDesc.depthStencil = nullptr;

	// Multi-sampling
	// Samples per pixel
	pipelineDesc.multisample.count = 1;
	// Default value for the mask, meaning "all bits on"
	pipelineDesc.multisample.mask = ~0u;
	// Default value as well (irrelevant for count = 1 anyways)
	pipelineDesc.multisample.alphaToCoverageEnabled = false;

	// Pipeline layout
	pipelineDesc.layout = nullptr;

	RenderPipeline pipeline = device.createRenderPipeline(pipelineDesc);
	std::cout << "Render pipeline: " << pipeline << std::endl;

	while (!glfwWindowShouldClose(window)) {
		glfwPollEvents();

		TextureView nextTexture = swapChain.getCurrentTextureView();
		if (!nextTexture) {
			std::cerr << "Cannot acquire next swap chain texture" << std::endl;
			return 1;
		}

		CommandEncoderDescriptor commandEncoderDesc;
		commandEncoderDesc.label = "Command Encoder";
		CommandEncoder encoder = device.createCommandEncoder(commandEncoderDesc);
		
		RenderPassDescriptor renderPassDesc;

		RenderPassColorAttachment renderPassColorAttachment;
		renderPassColorAttachment.view = nextTexture;
		renderPassColorAttachment.resolveTarget = nullptr;
		renderPassColorAttachment.loadOp = LoadOp::Clear;
		renderPassColorAttachment.storeOp = StoreOp::Store;
		renderPassColorAttachment.clearValue = Color{ 0.9, 0.1, 0.2, 1.0 };
		renderPassDesc.colorAttachmentCount = 1;
		renderPassDesc.colorAttachments = &renderPassColorAttachment;

		renderPassDesc.depthStencilAttachment = nullptr;
		renderPassDesc.timestampWriteCount = 0;
		renderPassDesc.timestampWrites = nullptr;
		RenderPassEncoder renderPass = encoder.beginRenderPass(renderPassDesc);

		// In its overall outline, drawing a triangle is as simple as this:
		// Select which render pipeline to use
		renderPass.setPipeline(pipeline);
		// Draw 1 instance of a 3-vertices shape
		renderPass.draw(3, 1, 0, 0);

		renderPass.end();
		
		nextTexture.release();

		CommandBufferDescriptor cmdBufferDescriptor;
		cmdBufferDescriptor.label = "Command buffer";
		CommandBuffer command = encoder.finish(cmdBufferDescriptor);
		queue.submit(command);

		swapChain.present();
	}

	swapChain.release();
	device.release();
	adapter.release();
	instance.release();
	glfwDestroyWindow(window);
	glfwTerminate();

	return 0;
}

在这里插入图片描述

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

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

相关文章

C# 全局响应Ctrl+Alt+鼠标右键

一、简述 某些应用&#xff0c;我们希望全局自定义热键。按键少了会和别的应用程序冲突&#xff0c;按键多了可定用户操作不变。因此我计划左手用CtrlAlt&#xff0c;右手用鼠标右键呼出我自定义的菜单。 我使用键盘和鼠标事件进行简单测试&#xff08;Ctrl鼠标右键&#xff…

TypeScript -- 函数

文章目录 TypeScript -- 函数JS -- 函数的两种表现形式函数声明函数的表达式es6 箭头函数 TS -- 定义一个函数TS -- 函数声明使用接口(定义)ts 定义参数可选参数写法 -- ?的使用TS函数 -- 设置剩余参数函数重载 TypeScript – 函数 JS – 函数的两种表现形式 我们熟知js有两…

MySQLExplain详解

Explain使用场景 查询性能优化&#xff1a;EXPLAIN可以帮助开发者分析查询语句的执行计划&#xff0c;判断是否有效地使用了索引、是否有可能导致全表扫描等性能问题。通过EXPLAIN的输出&#xff0c;可以找到潜在的性能瓶颈&#xff0c;并优化查询语句、创建合适的索引或调整表…

Win11虚拟机安装并使用

windows11 虚拟机安装 操作如下&#xff1a;1.进入微软官网2.打开虚拟机应用创建新虚拟机3.选择刚下载IOS文件4 设置虚拟机磁盘空间大小&#xff0c;这个数字可以随便写&#xff0c;反正都是虚拟的&#xff0c;但不可以低于64GB。下面的是否拆分磁盘文件&#xff0c;可更具需要…

大数据课程C4——ZooKeeper结构运行机制

文章作者邮箱&#xff1a;yugongshiyesina.cn 地址&#xff1a;广东惠州 ▲ 本章节目的 ⚪ 了解Zookeeper的特点和节点信息&#xff1b; ⚪ 掌握Zookeeper的完全分布式安装 ⚪ 掌握Zookeeper的选举机制、ZAB协议、AVRO&#xff1b; 一、Zookeeper-简介 1. 特点…

【计网】什么是三次握手四次挥手

文章目录 1、什么是TCP2、什么是TCP连接2.1、连接概念2.2、如何唯一确定一个TCP连接2.3、TCP最大连接数 3、三次握手3.1、为什么需要三次握手3.2、三次握手过程3.3、为什么一定是三次3.3.1、避免历史连接3.3.2、同步双方初始序列号3.3.3、避免资源浪费3.3.4、总结 3.4、握手丢失…

vue实现卡牌数字动态翻牌效果

vue实现卡牌数字动态翻牌效果 1. 实现效果2. 实现代码 1. 实现效果 在大屏项目中&#xff0c;我们尝尝会遇到卡牌式数字显示且能动态翻牌的效果&#xff0c;效果图如下&#xff1a; 2. 实现代码 <template><div class"days-box"><div class"op…

初探PID—速度闭环控制

由于在调PID时意外把板子烧了&#xff0c;目前只完成了比例调节的调试&#xff0c;整个程序也不太完善&#xff0c;本文当前仅作记录&#xff0c;后续会完善更改。 ——2023.07.26 文章目录 一、什么是PID二、PID有什么用三、PID程序实现 一、什么是PID PID是常用的一种控制算…

windows默认编码格式修改

1.命令提示符界面输入 chcp 936 对应 GBK 65001 对应 UTF-8 2.临时更改编码格式 chcp 936(或65001) 3.永久更改编码格式 依次开控制面板->时钟和区域->区域->管理->更改系统区域设置&#xff0c;然后按下图所示&#xff0c;勾选使用UTF-8语言支持。然后重启电脑。此…

上门小程序开发|上门服务小程序|上门家政小程序开发

随着移动互联网的普及和发展&#xff0c;上门服务成为了许多人生活中的一部分。上门小程序是一种基于小程序平台的应用程序&#xff0c;它提供了上门服务的在线平台&#xff0c;为用户提供了便捷的上门服务体验。下面将介绍一些适合开发上门小程序的商家。   家政服务商家&am…

帮助中心内容需要囊括什么?(内含案例分享)

给产品制作一个帮助中心&#xff0c;让用户能够通过访问帮助中心查看产品相关内容&#xff0c;尽快了解产品&#xff0c;熟悉操作。不仅仅局限于售后&#xff0c;在售中售前都能够发挥很大的作用&#xff0c;帮助用户全面了解产品&#xff0c;减少销售的工作量&#xff0c;节约…

数字孪生和 GIS 系统融合将为水利领域带来哪些变化?

随着科技的不断进步&#xff0c;数字孪生和 GIS 系统的融合应用逐渐成为了水利领域的新趋势。数字孪生是指通过数字化技术模拟物理实体和过程&#xff0c;将现实世界与虚拟世界相结合的技术&#xff0c;而 GIS 系统则是地理信息系统&#xff0c;用于收集、存储、管理和分析地理…

Mybatis快速入门,Mybatis的核心配置文件

Mybatis快速入门 一、Mybatis简介1.1Mybatis简化JDBC 二、Mybatis快速入门2.1创建user表&#xff0c;添加数据2.2创建模块&#xff0c;导入坐标2.3编写Mybatis核心配置文件 --> 替换连接信息&#xff0c;解决硬编码问题2.4编写SQL映射文件 --> 统一管理sql语句&#xff0…

7、Java入门教程【面向对象】

面向对象是Java编程的核心概念&#xff0c;如果不能充分了解面向对象的思想&#xff0c;那么会给你在实际的项目开发过程中&#xff0c;带来很多业务设计上的困扰。 一、构造器 我们在设计完一个类&#xff0c;在使用这个类去创建对象实例的时候&#xff0c;有些场景是需要对…

云曦暑期学习第二周——文件上传漏洞

1.文件上传 1.1原理 一些web应用程序中允许上传图片、视频、头像和许多其他类型的文件到服务器中。 文件上传漏洞就是利用服务端代码对文件上传路径变量过滤不严格将可执行的文件上传到一个到服务器中 &#xff0c;再通过URL去访问以执行恶意代码。 1.2为什么存在文件上传漏…

4-Linux组管理和权限管理

Linux组管理和权限管理 Linux组的基本介绍文件/目录的所有者组的创建文件/目录所在的组其它组改变用户所在的组权限的基本介绍第0-9位说明rwx权限详解rwx 修饰文件时rwx修饰目录时 修改权限第一种方式&#xff1a;、-、 变更权限第二种方式&#xff1a;通过数字变更权限 修改文…

第26天-秒杀服务(秒杀系统设计与实现)

1.秒杀设计 1.1.秒杀业务 秒杀具有瞬间高并发特点&#xff0c;针对这一特点&#xff0c;必须要做限流异步缓存&#xff08;页面静态化&#xff09;独立部署。 限流方式&#xff1a; 前端限流&#xff0c;一些高并发的网站直接在前端页面开始限流&#xff0c;例如&#xff1a…

web-文件包含

产生原因&#xff1a; 开发人员都希望代码更加灵活&#xff0c;所以通常会将被包含的文件设置为变量&#xff0c;用来进行动态调用。正是这种灵活性&#xff0c;从而导致客户端可以调用一个恶意文件&#xff0c;造成文件包含漏洞。 实际上被包含文件可以是任意格式的&#xff0…

【数据结构】带你轻松拿捏顺序表(内附源码)

君兮_的个人主页 勤时当勉励 岁月不待人 C/C 游戏开发 Hello,米娜桑们&#xff0c;这里是君兮_&#xff0c;今天正式开始开新坑啦&#xff01;在接下来的这一个月来我会逐步带大家了解初阶数据结构的知识&#xff0c;如果是你主修的是计算机专业数据结构的重要性不言而喻&…

深度学习论文: Q-YOLO: Efficient Inference for Real-time Object Detection及其PyTorch实现

深度学习论文: Q-YOLO: Efficient Inference for Real-time Object Detection及其PyTorch实现 Q-YOLO: Efficient Inference for Real-time Object Detection PDF: https://arxiv.org/pdf/2307.04816.pdf PyTorch代码: https://github.com/shanglianlm0525/CvPytorch PyTorch代…