Рендер у світі 26.1.2
Створіть та використовуйте власний конвеєр рендера коли стандартні конвеєри не підходять.
ПЕРЕДУМОВИ
Переконайтеся, що ви спочатку прочитали концепції рендера. Ця сторінка базується на цих концепціях і обговорює, як рендеряться об’єкти у світі.
На цій сторінці розглядаються деякі більш сучасні концепції рендера. Ви дізнаєтеся більше про дві розділені фази рендера: «вилучення» (або «підготовка») і «малювання» (або «рендер»). У цьому посібнику ми будемо називати фазу «вилучення/підготовки» фазою «вилучення», а фазу «малювання/рендера» — фазою «малювання».
Щоб рендерити власні об'єкти у світі, у вас є два варіанти. Ви можете вставити в наявний стандартний рендер та додати свій код, але це обмежує вас наявними стандартними конвеєрами рендера. Якщо наявні стандартні конвеєри рендера не відповідають вашим потребам, вам потрібен власний конвеєр рендера.
Перш ніж перейти до власних конвеєрів рендера, подивімося на стандартний рендер.
Фази вилучення та малювання
Як згадувалося в концепціях рендера, останні оновлення Minecraft працюють над розділенням рендера на дві фази: «вилучення» та «малювання».
Усі дані, необхідні для рендера, збираються під час фази «вилучення». Це включає, наприклад, доступ до даних світу. Зауважте, що попри те, що багато методів мають префікс draw або render, їх слід викликати під час фази «вилучення». Ви повинні додати всі елементи, які ви хочете рендерити на цьому етапі.
Після завершення фази «вилучення» починається фаза «малювання», і створено буферизований конструктор. Під час цієї фази буферизований конструктор промальовування на екрані. Кінцева мета цього розділення «вилучення» та «малювання» полягає в тому, щоб дозволити малювати попередній кадр паралельно з вилученням наступного кадру, покращуючи продуктивність.
Тепер, пам’ятаючи про ці дві фази, подивімось, як створити власний конвеєр рендера.
Власні конвеєри рендера
Скажімо, ми хочемо рендерити маршрутні точки, які мають з’являтися крізь стіни. Найближчим стандартним конвеєром для цього буде RenderPipelines#DEBUG_FILLED_BOX, але він не буде рендеритися через стіни, тому нам знадобиться спеціальний конвеєр рендера.
Визначення власного конвеєра рендера
Ми визначаємо власний конвеєр рендера в класі:
java
private static final RenderPipeline FILLED_THROUGH_WALLS = RenderPipelines.register(RenderPipeline.builder(RenderPipelines.DEBUG_FILLED_SNIPPET)
.withLocation(Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "pipeline/debug_filled_box_through_walls"))
.withDepthStencilState(Optional.empty())
.build()
);1
2
3
4
5
2
3
4
5
Фаза вилучення
Спочатку ми реалізуємо фазу «вилучення». Ми можемо викликати цей метод під час фази «вилучення», щоб додати маршрутну точку для рендера.
java
private static WaypointRenderState waypointState;
private void extractWaypoint(LevelExtractionContext context) {
// Access data from the world or anything here in the extraction phase.
// You can only access the (immutable and thread safe) render state in the drawing phase.
waypointState = new WaypointRenderState(0, 100, 0, 0f, 1f, 0f, 0.5f);
}
// Render states should be immutable, thread safe, and fast to create.
private record WaypointRenderState(int x, int y, int z, float r, float g, float b, float a) { }1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
Якщо ви хочете рендерити кілька маршрутних точок, змініть waypointState на список і додайте кілька станів рендера маршрутних точок. Переконайтеся, що ви робите це під час фази «вилучення», ПЕРЕД початком фази «малювання», на якій будується конструктор буферів.
Стани рендера
Зауважте, що у наведеному вище коді ми зберігаємо WaypointRenderState у полі. Це тому, що нам це потрібно на етапі «малювання». У цьому випадку WaypointRenderState — це наш «стан рендера» або «видобуті дані». Якщо вам потрібні додаткові дані (тобто зі світу) під час фази «малювання», ви повинні додати їх до свого спеціального класу стану рендера.
Фаза малювання
Тепер ми реалізуємо етап «малювання». Це слід викликати після того, як усі маршрутні точки, які ви хочете рендерити, були додані до waypointState під час фази «вилучення».
java
private static final ByteBufferBuilder ALLOCATOR = new ByteBufferBuilder(RenderType.SMALL_BUFFER_SIZE);
private static final Vector4f COLOR_MODULATOR = new Vector4f(1f, 1f, 1f, 1f);
private static final Vector3f MODEL_OFFSET = new Vector3f();
private static final Matrix4f TEXTURE_MATRIX = new Matrix4f();
private BufferBuilder buffer;
private MappableRingBuffer vertexBuffer;
private void renderAndDrawWaypoint(LevelRenderContext context) {
this.renderWaypoint(context);
this.drawFilledThroughWalls(Minecraft.getInstance(), FILLED_THROUGH_WALLS);
}
private void renderWaypoint(LevelRenderContext context) {
PoseStack matrices = context.poseStack();
Vec3 camera = context.levelState().cameraRenderState.pos;
matrices.pushPose();
matrices.translate(-camera.x, -camera.y, -camera.z);
if (this.buffer == null) {
this.buffer = new BufferBuilder(ALLOCATOR, FILLED_THROUGH_WALLS.getVertexFormatMode(), FILLED_THROUGH_WALLS.getVertexFormat());
}
this.renderFilledBox(matrices.last().pose(), this.buffer, waypointState.x(), waypointState.y(), waypointState.z(), waypointState.x() + 1, waypointState.y() + 1, waypointState.z() + 1, waypointState.r(), waypointState.g(), waypointState.b(), waypointState.a());
matrices.popPose();
}
private void renderFilledBox(Matrix4fc positionMatrix, BufferBuilder buffer, float minX, float minY, float minZ, float maxX, float maxY, float maxZ, float red, float green, float blue, float alpha) {
// Front Face
buffer.addVertex(positionMatrix, minX, minY, maxZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, maxX, minY, maxZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, maxX, maxY, maxZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, minX, maxY, maxZ).setColor(red, green, blue, alpha);
// Back face
buffer.addVertex(positionMatrix, maxX, minY, minZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, minX, minY, minZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, minX, maxY, minZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, maxX, maxY, minZ).setColor(red, green, blue, alpha);
// Left face
buffer.addVertex(positionMatrix, minX, minY, minZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, minX, minY, maxZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, minX, maxY, maxZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, minX, maxY, minZ).setColor(red, green, blue, alpha);
// Right face
buffer.addVertex(positionMatrix, maxX, minY, maxZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, maxX, minY, minZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, maxX, maxY, minZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, maxX, maxY, maxZ).setColor(red, green, blue, alpha);
// Top face
buffer.addVertex(positionMatrix, minX, maxY, maxZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, maxX, maxY, maxZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, maxX, maxY, minZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, minX, maxY, minZ).setColor(red, green, blue, alpha);
// Bottom face
buffer.addVertex(positionMatrix, minX, minY, minZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, maxX, minY, minZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, maxX, minY, maxZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, minX, minY, maxZ).setColor(red, green, blue, alpha);
}
private void drawFilledThroughWalls(Minecraft client, @SuppressWarnings("SameParameterValue") RenderPipeline pipeline) {
// Build the buffer
MeshData builtBuffer = this.buffer.buildOrThrow();
MeshData.DrawState drawParameters = builtBuffer.drawState();
VertexFormat format = drawParameters.format();
GpuBuffer vertices = this.upload(drawParameters, format, builtBuffer);
draw(client, pipeline, builtBuffer, drawParameters, vertices, format);
// Rotate the vertex buffer so we are less likely to use buffers that the GPU is using
this.vertexBuffer.rotate();
this.buffer = null;
}
private GpuBuffer upload(MeshData.DrawState drawParameters, VertexFormat format, MeshData builtBuffer) {
// Calculate the size needed for the vertex buffer
int vertexBufferSize = drawParameters.vertexCount() * format.getVertexSize();
// Initialize or resize the vertex buffer as needed
if (this.vertexBuffer == null || this.vertexBuffer.size() < vertexBufferSize) {
if (this.vertexBuffer != null) {
this.vertexBuffer.close();
}
this.vertexBuffer = new MappableRingBuffer(() -> ExampleMod.MOD_ID + " example render pipeline", GpuBuffer.USAGE_VERTEX | GpuBuffer.USAGE_MAP_WRITE, vertexBufferSize);
}
// Copy vertex data into the vertex buffer
CommandEncoder commandEncoder = RenderSystem.getDevice().createCommandEncoder();
try (GpuBuffer.MappedView mappedView = commandEncoder.mapBuffer(this.vertexBuffer.currentBuffer().slice(0, builtBuffer.vertexBuffer().remaining()), false, true)) {
MemoryUtil.memCopy(builtBuffer.vertexBuffer(), mappedView.data());
}
return this.vertexBuffer.currentBuffer();
}
private static void draw(Minecraft client, RenderPipeline pipeline, MeshData builtBuffer, MeshData.DrawState drawParameters, GpuBuffer vertices, VertexFormat format) {
GpuBuffer indices;
VertexFormat.IndexType indexType;
if (pipeline.getVertexFormatMode() == VertexFormat.Mode.QUADS) {
// Sort the quads if there is translucency
builtBuffer.sortQuads(ALLOCATOR, RenderSystem.getProjectionType().vertexSorting());
// Upload the index buffer
indices = pipeline.getVertexFormat().uploadImmediateIndexBuffer(builtBuffer.indexBuffer());
indexType = builtBuffer.drawState().indexType();
} else {
// Use the general shape index buffer for non-quad draw modes
RenderSystem.AutoStorageIndexBuffer shapeIndexBuffer = RenderSystem.getSequentialBuffer(pipeline.getVertexFormatMode());
indices = shapeIndexBuffer.getBuffer(drawParameters.indexCount());
indexType = shapeIndexBuffer.type();
}
// Actually execute the draw
GpuBufferSlice dynamicTransforms = RenderSystem.getDynamicUniforms()
.writeTransform(RenderSystem.getModelViewMatrix(), COLOR_MODULATOR, MODEL_OFFSET, TEXTURE_MATRIX);
try (RenderPass renderPass = RenderSystem.getDevice()
.createCommandEncoder()
.createRenderPass(() -> ExampleMod.MOD_ID + " example render pipeline rendering", client.getMainRenderTarget().getColorTextureView(), OptionalInt.empty(), client.getMainRenderTarget().getDepthTextureView(), OptionalDouble.empty())) {
renderPass.setPipeline(pipeline);
RenderSystem.bindDefaultUniforms(renderPass);
renderPass.setUniform("DynamicTransforms", dynamicTransforms);
// Bind texture if applicable:
// Sampler0 is used for texture inputs in vertices
// renderPass.bindTexture("Sampler0", textureSetup.texure0(), textureSetup.sampler0());
renderPass.setVertexBuffer(0, vertices);
renderPass.setIndexBuffer(indices, indexType);
// The base vertex is the starting index when we copied the data into the vertex buffer divided by vertex size
//noinspection ConstantValue
renderPass.drawIndexed(0 / format.getVertexSize(), 0, drawParameters.indexCount(), 1);
}
builtBuffer.close();
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
Зауважте, що розмір, який використовується в конструкторі ByteBufferBuilder, залежить від конвеєра рендера, який ви використовуєте. У нашому випадку це RenderType.SMALL_BUFFER_SIZE.
Очищення
Нарешті, нам потрібно очистити ресурси, коли ігровий рендер закінчено. GameRenderer#close має викликати цей метод, і для цього вам наразі потрібно вставити в GameRenderer#close за допомогою міксина.
java
public void close() {
ALLOCATOR.close();
if (this.vertexBuffer != null) {
this.vertexBuffer.close();
this.vertexBuffer = null;
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
java
package com.example.docs.mixin.client;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.client.renderer.GameRenderer;
import com.example.docs.rendering.CustomRenderPipeline;
@Mixin(GameRenderer.class)
public class GameRendererMixin {
@Inject(method = "close", at = @At("RETURN"))
private void onGameRendererClose(CallbackInfo ci) {
CustomRenderPipeline.getInstance().close();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Остаточний код
Об'єднавши всі описані вище кроки, ми отримаємо простий клас, який рендерить маршрутну точку на (0, 100, 0) через стіни.
java
package com.example.docs.rendering;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import com.mojang.blaze3d.buffers.GpuBuffer;
import com.mojang.blaze3d.buffers.GpuBufferSlice;
import com.mojang.blaze3d.pipeline.RenderPipeline;
import com.mojang.blaze3d.systems.CommandEncoder;
import com.mojang.blaze3d.systems.RenderPass;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.ByteBufferBuilder;
import com.mojang.blaze3d.vertex.MeshData;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexFormat;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.joml.Vector3f;
import org.joml.Vector4f;
import org.lwjgl.system.MemoryUtil;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.MappableRingBuffer;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.client.renderer.rendertype.RenderType;
import net.minecraft.resources.Identifier;
import net.minecraft.world.phys.Vec3;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.rendering.v1.level.LevelExtractionContext;
import net.fabricmc.fabric.api.client.rendering.v1.level.LevelRenderContext;
import net.fabricmc.fabric.api.client.rendering.v1.level.LevelRenderEvents;
import com.example.docs.ExampleMod;
public class CustomRenderPipeline implements ClientModInitializer {
private static CustomRenderPipeline instance;
private static final RenderPipeline FILLED_THROUGH_WALLS = RenderPipelines.register(RenderPipeline.builder(RenderPipelines.DEBUG_FILLED_SNIPPET)
.withLocation(Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "pipeline/debug_filled_box_through_walls"))
.withDepthStencilState(Optional.empty())
.build()
);
private static WaypointRenderState waypointState;
private static final ByteBufferBuilder ALLOCATOR = new ByteBufferBuilder(RenderType.SMALL_BUFFER_SIZE);
private static final Vector4f COLOR_MODULATOR = new Vector4f(1f, 1f, 1f, 1f);
private static final Vector3f MODEL_OFFSET = new Vector3f();
private static final Matrix4f TEXTURE_MATRIX = new Matrix4f();
private BufferBuilder buffer;
private MappableRingBuffer vertexBuffer;
public static CustomRenderPipeline getInstance() {
return instance;
}
@Override
public void onInitializeClient() {
instance = this;
LevelRenderEvents.END_EXTRACTION.register(this::extractWaypoint);
LevelRenderEvents.AFTER_TRANSLUCENT_TERRAIN.register(this::renderAndDrawWaypoint);
}
private void extractWaypoint(LevelExtractionContext context) {
// Access data from the world or anything here in the extraction phase.
// You can only access the (immutable and thread safe) render state in the drawing phase.
waypointState = new WaypointRenderState(0, 100, 0, 0f, 1f, 0f, 0.5f);
}
private void renderAndDrawWaypoint(LevelRenderContext context) {
this.renderWaypoint(context);
this.drawFilledThroughWalls(Minecraft.getInstance(), FILLED_THROUGH_WALLS);
}
private void renderWaypoint(LevelRenderContext context) {
PoseStack matrices = context.poseStack();
Vec3 camera = context.levelState().cameraRenderState.pos;
matrices.pushPose();
matrices.translate(-camera.x, -camera.y, -camera.z);
if (this.buffer == null) {
this.buffer = new BufferBuilder(ALLOCATOR, FILLED_THROUGH_WALLS.getVertexFormatMode(), FILLED_THROUGH_WALLS.getVertexFormat());
}
this.renderFilledBox(matrices.last().pose(), this.buffer, waypointState.x(), waypointState.y(), waypointState.z(), waypointState.x() + 1, waypointState.y() + 1, waypointState.z() + 1, waypointState.r(), waypointState.g(), waypointState.b(), waypointState.a());
matrices.popPose();
}
private void renderFilledBox(Matrix4fc positionMatrix, BufferBuilder buffer, float minX, float minY, float minZ, float maxX, float maxY, float maxZ, float red, float green, float blue, float alpha) {
// Front Face
buffer.addVertex(positionMatrix, minX, minY, maxZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, maxX, minY, maxZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, maxX, maxY, maxZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, minX, maxY, maxZ).setColor(red, green, blue, alpha);
// Back face
buffer.addVertex(positionMatrix, maxX, minY, minZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, minX, minY, minZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, minX, maxY, minZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, maxX, maxY, minZ).setColor(red, green, blue, alpha);
// Left face
buffer.addVertex(positionMatrix, minX, minY, minZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, minX, minY, maxZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, minX, maxY, maxZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, minX, maxY, minZ).setColor(red, green, blue, alpha);
// Right face
buffer.addVertex(positionMatrix, maxX, minY, maxZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, maxX, minY, minZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, maxX, maxY, minZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, maxX, maxY, maxZ).setColor(red, green, blue, alpha);
// Top face
buffer.addVertex(positionMatrix, minX, maxY, maxZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, maxX, maxY, maxZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, maxX, maxY, minZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, minX, maxY, minZ).setColor(red, green, blue, alpha);
// Bottom face
buffer.addVertex(positionMatrix, minX, minY, minZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, maxX, minY, minZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, maxX, minY, maxZ).setColor(red, green, blue, alpha);
buffer.addVertex(positionMatrix, minX, minY, maxZ).setColor(red, green, blue, alpha);
}
private void drawFilledThroughWalls(Minecraft client, @SuppressWarnings("SameParameterValue") RenderPipeline pipeline) {
// Build the buffer
MeshData builtBuffer = this.buffer.buildOrThrow();
MeshData.DrawState drawParameters = builtBuffer.drawState();
VertexFormat format = drawParameters.format();
GpuBuffer vertices = this.upload(drawParameters, format, builtBuffer);
draw(client, pipeline, builtBuffer, drawParameters, vertices, format);
// Rotate the vertex buffer so we are less likely to use buffers that the GPU is using
this.vertexBuffer.rotate();
this.buffer = null;
}
private GpuBuffer upload(MeshData.DrawState drawParameters, VertexFormat format, MeshData builtBuffer) {
// Calculate the size needed for the vertex buffer
int vertexBufferSize = drawParameters.vertexCount() * format.getVertexSize();
// Initialize or resize the vertex buffer as needed
if (this.vertexBuffer == null || this.vertexBuffer.size() < vertexBufferSize) {
if (this.vertexBuffer != null) {
this.vertexBuffer.close();
}
this.vertexBuffer = new MappableRingBuffer(() -> ExampleMod.MOD_ID + " example render pipeline", GpuBuffer.USAGE_VERTEX | GpuBuffer.USAGE_MAP_WRITE, vertexBufferSize);
}
// Copy vertex data into the vertex buffer
CommandEncoder commandEncoder = RenderSystem.getDevice().createCommandEncoder();
try (GpuBuffer.MappedView mappedView = commandEncoder.mapBuffer(this.vertexBuffer.currentBuffer().slice(0, builtBuffer.vertexBuffer().remaining()), false, true)) {
MemoryUtil.memCopy(builtBuffer.vertexBuffer(), mappedView.data());
}
return this.vertexBuffer.currentBuffer();
}
private static void draw(Minecraft client, RenderPipeline pipeline, MeshData builtBuffer, MeshData.DrawState drawParameters, GpuBuffer vertices, VertexFormat format) {
GpuBuffer indices;
VertexFormat.IndexType indexType;
if (pipeline.getVertexFormatMode() == VertexFormat.Mode.QUADS) {
// Sort the quads if there is translucency
builtBuffer.sortQuads(ALLOCATOR, RenderSystem.getProjectionType().vertexSorting());
// Upload the index buffer
indices = pipeline.getVertexFormat().uploadImmediateIndexBuffer(builtBuffer.indexBuffer());
indexType = builtBuffer.drawState().indexType();
} else {
// Use the general shape index buffer for non-quad draw modes
RenderSystem.AutoStorageIndexBuffer shapeIndexBuffer = RenderSystem.getSequentialBuffer(pipeline.getVertexFormatMode());
indices = shapeIndexBuffer.getBuffer(drawParameters.indexCount());
indexType = shapeIndexBuffer.type();
}
// Actually execute the draw
GpuBufferSlice dynamicTransforms = RenderSystem.getDynamicUniforms()
.writeTransform(RenderSystem.getModelViewMatrix(), COLOR_MODULATOR, MODEL_OFFSET, TEXTURE_MATRIX);
try (RenderPass renderPass = RenderSystem.getDevice()
.createCommandEncoder()
.createRenderPass(() -> ExampleMod.MOD_ID + " example render pipeline rendering", client.getMainRenderTarget().getColorTextureView(), OptionalInt.empty(), client.getMainRenderTarget().getDepthTextureView(), OptionalDouble.empty())) {
renderPass.setPipeline(pipeline);
RenderSystem.bindDefaultUniforms(renderPass);
renderPass.setUniform("DynamicTransforms", dynamicTransforms);
// Bind texture if applicable:
// Sampler0 is used for texture inputs in vertices
// renderPass.bindTexture("Sampler0", textureSetup.texure0(), textureSetup.sampler0());
renderPass.setVertexBuffer(0, vertices);
renderPass.setIndexBuffer(indices, indexType);
// The base vertex is the starting index when we copied the data into the vertex buffer divided by vertex size
//noinspection ConstantValue
renderPass.drawIndexed(0 / format.getVertexSize(), 0, drawParameters.indexCount(), 1);
}
builtBuffer.close();
}
public void close() {
ALLOCATOR.close();
if (this.vertexBuffer != null) {
this.vertexBuffer.close();
this.vertexBuffer = null;
}
}
// Render states should be immutable, thread safe, and fast to create.
private record WaypointRenderState(int x, int y, int z, float r, float g, float b, float a) { }
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
Не забудьте також про GameRendererMixin! Ось результат:



