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