-
-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathPipelineManager.ts
144 lines (121 loc) · 6.38 KB
/
PipelineManager.ts
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
import { RenderPipelineEntry } from './RenderPipelineEntry'
import { ComputePipelineEntry } from './ComputePipelineEntry'
import { PipelineEntryParams, RenderPipelineEntryParams } from '../../types/PipelineEntries'
import { ShaderOptions } from '../../types/Materials'
/** Defines all types of allowed {@link core/pipelines/PipelineEntry.PipelineEntry | PipelineEntry} class objects */
export type AllowedPipelineEntries = RenderPipelineEntry | ComputePipelineEntry
/**
* Used to create and keep track of both {@link ComputePipelineEntry} and {@link RenderPipelineEntry}.<br>
* Perform checks to eventually use a cached pipeline entry instead of creating a new one.<br>
* The end goal is to cache pipelines and reuse them (as well as bind groups).<br>
* Also responsible for setting the current pass encoder pipeline in order to avoid redundant setPipeline calls.<br>
* Created internally by the {@link core/renderers/GPUDeviceManager.GPUDeviceManager | GPUDeviceManager}.<br>
* @see {@link https://toji.dev/webgpu-best-practices/bind-groups#grouping-resources-based-on-frequency-of-change | WebGPU Bind Group best practices}
*/
export class PipelineManager {
/** The type of the {@link PipelineManager} */
type: string
/** Keep track of the current bound pipeline in order to avoid redundant setPipeline calls */
currentPipelineIndex: number | null
/** Array of already created {@link ComputePipelineEntry} and {@link RenderPipelineEntry} */
pipelineEntries: AllowedPipelineEntries[]
constructor() {
this.type = 'PipelineManager'
this.currentPipelineIndex = null
this.pipelineEntries = []
}
/**
* Compare two {@link ShaderOptions | shader objects}
* @param shaderA - first {@link ShaderOptions | shader object} to compare
* @param shaderB - second {@link ShaderOptions | shader object} to compare
* @returns - whether the two {@link ShaderOptions | shader objects} code and entryPoint match
*/
compareShaders(shaderA: ShaderOptions, shaderB: ShaderOptions): boolean {
return shaderA.code?.localeCompare(shaderB.code) === 0 && shaderA.entryPoint === shaderB.entryPoint
}
/**
* Checks if the provided {@link RenderPipelineEntryParams | RenderPipelineEntry parameters} belongs to an already created {@link RenderPipelineEntry}.
* @param parameters - {@link RenderPipelineEntryParams | RenderPipelineEntry parameters}
* @returns - the found {@link RenderPipelineEntry}, or null if not found
*/
isSameRenderPipeline(parameters: RenderPipelineEntryParams): RenderPipelineEntry | null {
return this.pipelineEntries
.filter((pipelineEntry) => pipelineEntry instanceof RenderPipelineEntry)
.find((pipelineEntry: RenderPipelineEntry) => {
const { options } = pipelineEntry
const { shaders, rendering } = parameters
const sameVertexShader = this.compareShaders(shaders.vertex, options.shaders.vertex)
const sameFragmentShader =
(!shaders.fragment && !options.shaders.fragment) ||
this.compareShaders(shaders.fragment as ShaderOptions, options.shaders.fragment as ShaderOptions)
const differentParams = Object.keys(options.rendering).filter(
(key) => options.rendering[key] !== rendering[key]
)
// TODO might break with unused bindings!
return !differentParams.length && sameVertexShader && sameFragmentShader
}) as RenderPipelineEntry | null
}
/**
* Check if a {@link RenderPipelineEntry} has already been created with the given {@link RenderPipelineEntryParams | parameters}.
* Use it if found, else create a new one and add it to the {@link pipelineEntries} array.
* @param parameters - {@link RenderPipelineEntryParams | RenderPipelineEntry parameters}
* @returns - {@link RenderPipelineEntry}, either from cache or newly created
*/
createRenderPipeline(parameters: RenderPipelineEntryParams): RenderPipelineEntry {
const existingPipelineEntry = this.isSameRenderPipeline(parameters)
if (existingPipelineEntry) {
return existingPipelineEntry
} else {
const pipelineEntry = new RenderPipelineEntry(parameters)
this.pipelineEntries.push(pipelineEntry)
return pipelineEntry
}
}
/**
* Checks if the provided {@link PipelineEntryParams | parameters} belongs to an already created {@link ComputePipelineEntry}.
* @param parameters - {@link PipelineEntryParams | PipelineEntry parameters}
* @returns - the found {@link ComputePipelineEntry}, or null if not found
*/
isSameComputePipeline(parameters: PipelineEntryParams) {
const { shaders } = parameters
return this.pipelineEntries
.filter((pipelineEntry) => pipelineEntry instanceof ComputePipelineEntry)
.find((pipelineEntry: ComputePipelineEntry) => {
const { options } = pipelineEntry
return this.compareShaders(shaders.compute, options.shaders.compute)
}) as ComputePipelineEntry | null
}
/**
* Check if a {@link ComputePipelineEntry} has already been created with the given {@link PipelineEntryParams | parameters}.
* Use it if found, else create a new one and add it to the {@link pipelineEntries} array.
* @param parameters - {@link PipelineEntryParams | PipelineEntry parameters}
* @returns - newly created {@link ComputePipelineEntry}
*/
createComputePipeline(parameters: PipelineEntryParams): ComputePipelineEntry {
const existingPipelineEntry = this.isSameComputePipeline(parameters)
if (existingPipelineEntry) {
return existingPipelineEntry
} else {
const pipelineEntry = new ComputePipelineEntry(parameters)
this.pipelineEntries.push(pipelineEntry)
return pipelineEntry
}
}
/**
* Check if the given {@link AllowedPipelineEntries | PipelineEntry} is already set, if not set it
* @param pass - current pass encoder
* @param pipelineEntry - the {@link AllowedPipelineEntries | PipelineEntry} to set
*/
setCurrentPipeline(pass: GPURenderPassEncoder | GPUComputePassEncoder, pipelineEntry: AllowedPipelineEntries) {
if (pipelineEntry.index !== this.currentPipelineIndex) {
pass.setPipeline(pipelineEntry.pipeline as GPURenderPipeline & GPUComputePipeline)
this.currentPipelineIndex = pipelineEntry.index
}
}
/**
* Reset the {@link PipelineManager#currentPipelineIndex | current pipeline index} so the next {@link AllowedPipelineEntries | PipelineEntry} will be set for sure
*/
resetCurrentPipeline() {
this.currentPipelineIndex = null
}
}