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