1use std::cell::Cell;
7use std::os::raw::c_int;
8use std::sync::Once;
9
10use dom_struct::dom_struct;
11use mozangle::shaders::{BuiltInResources, CompileOptions, Output, ShaderValidator};
12use script_bindings::cell::DomRefCell;
13use script_bindings::reflector::reflect_dom_object;
14use script_bindings::weakref::WeakRef;
15use servo_canvas_traits::webgl::{
16 GLLimits, GlType, WebGLCommand, WebGLError, WebGLResult, WebGLSLVersion, WebGLShaderId,
17 WebGLVersion, webgl_channel,
18};
19
20use crate::dom::bindings::inheritance::Castable;
21use crate::dom::bindings::reflector::DomGlobal;
22use crate::dom::bindings::root::DomRoot;
23use crate::dom::bindings::str::DOMString;
24use crate::dom::webgl::extensions::WebGLExtensions;
25use crate::dom::webgl::extensions::extfragdepth::EXTFragDepth;
26use crate::dom::webgl::extensions::extshadertexturelod::EXTShaderTextureLod;
27use crate::dom::webgl::extensions::oesstandardderivatives::OESStandardDerivatives;
28use crate::dom::webgl::webglobject::WebGLObject;
29use crate::dom::webgl::webglrenderingcontext::{Operation, WebGLRenderingContext};
30use crate::dom::webglrenderingcontext::capture_webgl_backtrace;
31use crate::script_runtime::CanGc;
32
33#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
34pub(crate) enum ShaderCompilationStatus {
35 NotCompiled,
36 Succeeded,
37 Failed,
38}
39
40#[derive(JSTraceable, MallocSizeOf)]
41struct DroppableWebGLShader {
42 context: WeakRef<WebGLRenderingContext>,
43 #[no_trace]
44 id: WebGLShaderId,
45 marked_for_deletion: Cell<bool>,
46}
47
48impl DroppableWebGLShader {
49 fn send_with_fallibility(&self, command: WebGLCommand, fallibility: Operation) {
50 if let Some(root) = self.context.root() {
51 let result = root.sender().send(command, capture_webgl_backtrace());
52 if matches!(fallibility, Operation::Infallible) {
53 result.expect("Operation failed");
54 }
55 }
56 }
57
58 fn mark_for_deletion(&self, operation_fallibility: Operation) {
59 if !self.marked_for_deletion.get() {
60 self.marked_for_deletion.set(true);
61 self.send_with_fallibility(WebGLCommand::DeleteShader(self.id), operation_fallibility);
62 }
63 }
64}
65
66impl Drop for DroppableWebGLShader {
67 fn drop(&mut self) {
68 self.mark_for_deletion(Operation::Fallible);
69 }
70}
71
72#[dom_struct(associated_memory)]
73pub(crate) struct WebGLShader {
74 webgl_object: WebGLObject,
75 gl_type: u32,
76 source: DomRefCell<DOMString>,
77 info_log: DomRefCell<DOMString>,
78 attached_counter: Cell<u32>,
79 compilation_status: Cell<ShaderCompilationStatus>,
80 droppable: DroppableWebGLShader,
81}
82
83static GLSLANG_INITIALIZATION: Once = Once::new();
84
85impl WebGLShader {
86 fn new_inherited(context: &WebGLRenderingContext, id: WebGLShaderId, shader_type: u32) -> Self {
87 GLSLANG_INITIALIZATION.call_once(|| ::mozangle::shaders::initialize().unwrap());
88 Self {
89 webgl_object: WebGLObject::new_inherited(context),
90 gl_type: shader_type,
91 source: Default::default(),
92 info_log: Default::default(),
93 attached_counter: Cell::new(0),
94 compilation_status: Cell::new(ShaderCompilationStatus::NotCompiled),
95 droppable: DroppableWebGLShader {
96 context: WeakRef::new(context),
97 id,
98 marked_for_deletion: Cell::new(false),
99 },
100 }
101 }
102
103 pub(crate) fn maybe_new(
104 context: &WebGLRenderingContext,
105 shader_type: u32,
106 ) -> Option<DomRoot<Self>> {
107 let (sender, receiver) = webgl_channel().unwrap();
108 context.send_command(WebGLCommand::CreateShader(shader_type, sender));
109 receiver
110 .recv()
111 .unwrap()
112 .map(|id| WebGLShader::new(context, id, shader_type, CanGc::deprecated_note()))
113 }
114
115 pub(crate) fn new(
116 context: &WebGLRenderingContext,
117 id: WebGLShaderId,
118 shader_type: u32,
119 can_gc: CanGc,
120 ) -> DomRoot<Self> {
121 reflect_dom_object(
122 Box::new(WebGLShader::new_inherited(context, id, shader_type)),
123 &*context.global(),
124 can_gc,
125 )
126 }
127}
128
129impl WebGLShader {
130 pub(crate) fn id(&self) -> WebGLShaderId {
131 self.droppable.id
132 }
133
134 pub(crate) fn gl_type(&self) -> u32 {
135 self.gl_type
136 }
137
138 pub(crate) fn compile(
140 &self,
141 api_type: GlType,
142 webgl_version: WebGLVersion,
143 glsl_version: WebGLSLVersion,
144 limits: &GLLimits,
145 ext: &WebGLExtensions,
146 ) -> WebGLResult<()> {
147 if self.droppable.marked_for_deletion.get() && !self.is_attached() {
148 return Err(WebGLError::InvalidValue);
149 }
150 if self.compilation_status.get() != ShaderCompilationStatus::NotCompiled {
151 debug!("Compiling already compiled shader {}", self.id());
152 }
153
154 let source = self.source.borrow();
155
156 let mut params = BuiltInResources {
157 MaxVertexAttribs: limits.max_vertex_attribs as c_int,
158 MaxVertexUniformVectors: limits.max_vertex_uniform_vectors as c_int,
159 MaxVertexTextureImageUnits: limits.max_vertex_texture_image_units as c_int,
160 MaxCombinedTextureImageUnits: limits.max_combined_texture_image_units as c_int,
161 MaxTextureImageUnits: limits.max_texture_image_units as c_int,
162 MaxFragmentUniformVectors: limits.max_fragment_uniform_vectors as c_int,
163
164 MaxVertexOutputVectors: limits.max_vertex_output_vectors as c_int,
165 MaxFragmentInputVectors: limits.max_fragment_input_vectors as c_int,
166 MaxVaryingVectors: limits.max_varying_vectors as c_int,
167
168 OES_standard_derivatives: ext.is_enabled::<OESStandardDerivatives>() as c_int,
169 EXT_shader_texture_lod: ext.is_enabled::<EXTShaderTextureLod>() as c_int,
170 EXT_frag_depth: ext.is_enabled::<EXTFragDepth>() as c_int,
171
172 FragmentPrecisionHigh: 1,
173 ..Default::default()
174 };
175
176 if webgl_version == WebGLVersion::WebGL2 {
177 params.MinProgramTexelOffset = limits.min_program_texel_offset as c_int;
178 params.MaxProgramTexelOffset = limits.max_program_texel_offset as c_int;
179 params.MaxDrawBuffers = limits.max_draw_buffers as c_int;
180 }
181
182 let validator = match webgl_version {
183 WebGLVersion::WebGL1 => {
184 let output_format = if api_type == GlType::Gles {
185 Output::Essl
186 } else {
187 Output::Glsl
188 };
189 ShaderValidator::for_webgl(self.gl_type, output_format, ¶ms).unwrap()
190 },
191 WebGLVersion::WebGL2 => {
192 let output_format = if api_type == GlType::Gles {
193 Output::Essl
194 } else {
195 match (glsl_version.major, glsl_version.minor) {
196 (1, 30) => Output::Glsl130,
197 (1, 40) => Output::Glsl140,
198 (1, 50) => Output::Glsl150Core,
199 (3, 30) => Output::Glsl330Core,
200 (4, 0) => Output::Glsl400Core,
201 (4, 10) => Output::Glsl410Core,
202 (4, 20) => Output::Glsl420Core,
203 (4, 30) => Output::Glsl430Core,
204 (4, 40) => Output::Glsl440Core,
205 (4, _) => Output::Glsl450Core,
206 _ => Output::Glsl140,
207 }
208 };
209 ShaderValidator::for_webgl2(self.gl_type, output_format, ¶ms).unwrap()
210 },
211 };
212
213 let mut options = CompileOptions::mozangle();
216 options.set_variables(1);
217 options.set_enforcePackingRestrictions(1);
218 options.set_objectCode(1);
219 options.set_initGLPosition(1);
220 options.set_initializeUninitializedLocals(1);
221 options.set_initOutputVariables(1);
222
223 options.set_limitExpressionComplexity(1);
224 options.set_limitCallStackDepth(1);
225
226 if cfg!(target_os = "macos") {
227 options.set_removeInvariantAndCentroidForESSL3(1);
228
229 options.set_unfoldShortCircuit(1);
232 options.set_regenerateStructNames(1);
234 options.set_addAndTrueToLoopCondition(1);
237 options.set_rewriteTexelFetchOffsetToTexelFetch(1);
238 } else {
239 options.set_clampIndirectArrayBounds(1);
243 }
244
245 match validator.compile(&[&source.str()], options) {
246 Ok(()) => {
247 let translated_source = validator.object_code();
248 debug!("Shader translated: {}", translated_source);
249 self.upcast()
253 .send_command(WebGLCommand::CompileShader(self.id(), translated_source));
254 self.compilation_status
255 .set(ShaderCompilationStatus::Succeeded);
256 },
257 Err(error) => {
258 self.compilation_status.set(ShaderCompilationStatus::Failed);
259 debug!("Shader {} compilation failed: {}", self.id(), error);
260 },
261 }
262
263 *self.info_log.borrow_mut() = validator.info_log().into();
264
265 Ok(())
266 }
267
268 pub(crate) fn mark_for_deletion(&self, operation_fallibility: Operation) {
272 self.droppable.mark_for_deletion(operation_fallibility);
273 }
274
275 pub(crate) fn is_marked_for_deletion(&self) -> bool {
276 self.droppable.marked_for_deletion.get()
277 }
278
279 pub(crate) fn is_deleted(&self) -> bool {
280 self.droppable.marked_for_deletion.get() && !self.is_attached()
281 }
282
283 pub(crate) fn is_attached(&self) -> bool {
284 self.attached_counter.get() > 0
285 }
286
287 pub(crate) fn increment_attached_counter(&self) {
288 self.attached_counter.set(self.attached_counter.get() + 1);
289 }
290
291 pub(crate) fn decrement_attached_counter(&self) {
292 assert!(self.attached_counter.get() > 0);
293 self.attached_counter.set(self.attached_counter.get() - 1);
294 }
295
296 pub(crate) fn info_log(&self) -> DOMString {
298 self.info_log.borrow().clone()
299 }
300
301 pub(crate) fn source(&self) -> DOMString {
303 self.source.borrow().clone()
304 }
305
306 pub(crate) fn set_source(&self, source: DOMString) {
308 *self.source.borrow_mut() = source;
309 }
310
311 pub(crate) fn successfully_compiled(&self) -> bool {
312 self.compilation_status.get() == ShaderCompilationStatus::Succeeded
313 }
314}