1use std::cell::Cell;
7use std::os::raw::c_int;
8use std::sync::Once;
9
10use dom_struct::dom_struct;
11use js::context::JSContext;
12use mozangle::shaders::{BuiltInResources, CompileOptions, Output, ShaderValidator};
13use script_bindings::cell::DomRefCell;
14use script_bindings::reflector::reflect_dom_object_with_cx;
15use script_bindings::weakref::WeakRef;
16use servo_canvas_traits::webgl::{
17 GLLimits, GlType, WebGLCommand, WebGLError, WebGLResult, WebGLSLVersion, WebGLShaderId,
18 WebGLVersion, webgl_channel,
19};
20
21use crate::dom::bindings::inheritance::Castable;
22use crate::dom::bindings::reflector::DomGlobal;
23use crate::dom::bindings::root::DomRoot;
24use crate::dom::bindings::str::DOMString;
25use crate::dom::webgl::extensions::WebGLExtensions;
26use crate::dom::webgl::extensions::extfragdepth::EXTFragDepth;
27use crate::dom::webgl::extensions::extshadertexturelod::EXTShaderTextureLod;
28use crate::dom::webgl::extensions::oesstandardderivatives::OESStandardDerivatives;
29use crate::dom::webgl::webglobject::WebGLObject;
30use crate::dom::webgl::webglrenderingcontext::{Operation, WebGLRenderingContext};
31use crate::dom::webglrenderingcontext::capture_webgl_backtrace;
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 cx: &mut JSContext,
105 context: &WebGLRenderingContext,
106 shader_type: u32,
107 ) -> Option<DomRoot<Self>> {
108 let (sender, receiver) = webgl_channel().unwrap();
109 context.send_command(WebGLCommand::CreateShader(shader_type, sender));
110 receiver
111 .recv()
112 .unwrap()
113 .map(|id| WebGLShader::new(cx, context, id, shader_type))
114 }
115
116 pub(crate) fn new(
117 cx: &mut JSContext,
118 context: &WebGLRenderingContext,
119 id: WebGLShaderId,
120 shader_type: u32,
121 ) -> DomRoot<Self> {
122 reflect_dom_object_with_cx(
123 Box::new(WebGLShader::new_inherited(context, id, shader_type)),
124 &*context.global(),
125 cx,
126 )
127 }
128}
129
130impl WebGLShader {
131 pub(crate) fn id(&self) -> WebGLShaderId {
132 self.droppable.id
133 }
134
135 pub(crate) fn gl_type(&self) -> u32 {
136 self.gl_type
137 }
138
139 pub(crate) fn compile(
141 &self,
142 api_type: GlType,
143 webgl_version: WebGLVersion,
144 glsl_version: WebGLSLVersion,
145 limits: &GLLimits,
146 ext: &WebGLExtensions,
147 ) -> WebGLResult<()> {
148 if self.droppable.marked_for_deletion.get() && !self.is_attached() {
149 return Err(WebGLError::InvalidValue);
150 }
151 if self.compilation_status.get() != ShaderCompilationStatus::NotCompiled {
152 debug!("Compiling already compiled shader {}", self.id());
153 }
154
155 let source = self.source.borrow();
156
157 let mut params = BuiltInResources {
158 MaxVertexAttribs: limits.max_vertex_attribs as c_int,
159 MaxVertexUniformVectors: limits.max_vertex_uniform_vectors as c_int,
160 MaxVertexTextureImageUnits: limits.max_vertex_texture_image_units as c_int,
161 MaxCombinedTextureImageUnits: limits.max_combined_texture_image_units as c_int,
162 MaxTextureImageUnits: limits.max_texture_image_units as c_int,
163 MaxFragmentUniformVectors: limits.max_fragment_uniform_vectors as c_int,
164
165 MaxVertexOutputVectors: limits.max_vertex_output_vectors as c_int,
166 MaxFragmentInputVectors: limits.max_fragment_input_vectors as c_int,
167 MaxVaryingVectors: limits.max_varying_vectors as c_int,
168
169 OES_standard_derivatives: ext.is_enabled::<OESStandardDerivatives>() as c_int,
170 EXT_shader_texture_lod: ext.is_enabled::<EXTShaderTextureLod>() as c_int,
171 EXT_frag_depth: ext.is_enabled::<EXTFragDepth>() as c_int,
172
173 FragmentPrecisionHigh: 1,
174 ..Default::default()
175 };
176
177 if webgl_version == WebGLVersion::WebGL2 {
178 params.MinProgramTexelOffset = limits.min_program_texel_offset as c_int;
179 params.MaxProgramTexelOffset = limits.max_program_texel_offset as c_int;
180 params.MaxDrawBuffers = limits.max_draw_buffers as c_int;
181 }
182
183 let validator = match webgl_version {
184 WebGLVersion::WebGL1 => {
185 let output_format = if api_type == GlType::Gles {
186 Output::Essl
187 } else {
188 Output::Glsl
189 };
190 ShaderValidator::for_webgl(self.gl_type, output_format, ¶ms).unwrap()
191 },
192 WebGLVersion::WebGL2 => {
193 let output_format = if api_type == GlType::Gles {
194 Output::Essl
195 } else {
196 match (glsl_version.major, glsl_version.minor) {
197 (1, 30) => Output::Glsl130,
198 (1, 40) => Output::Glsl140,
199 (1, 50) => Output::Glsl150Core,
200 (3, 30) => Output::Glsl330Core,
201 (4, 0) => Output::Glsl400Core,
202 (4, 10) => Output::Glsl410Core,
203 (4, 20) => Output::Glsl420Core,
204 (4, 30) => Output::Glsl430Core,
205 (4, 40) => Output::Glsl440Core,
206 (4, _) => Output::Glsl450Core,
207 _ => Output::Glsl140,
208 }
209 };
210 ShaderValidator::for_webgl2(self.gl_type, output_format, ¶ms).unwrap()
211 },
212 };
213
214 let mut options = CompileOptions::mozangle();
217 options.set_variables(1);
218 options.set_enforcePackingRestrictions(1);
219 options.set_objectCode(1);
220 options.set_initGLPosition(1);
221 options.set_initializeUninitializedLocals(1);
222 options.set_initOutputVariables(1);
223
224 options.set_limitExpressionComplexity(1);
225 options.set_limitCallStackDepth(1);
226
227 if cfg!(target_os = "macos") {
228 options.set_removeInvariantAndCentroidForESSL3(1);
229
230 options.set_unfoldShortCircuit(1);
233 options.set_regenerateStructNames(1);
235 options.set_addAndTrueToLoopCondition(1);
238 options.set_rewriteTexelFetchOffsetToTexelFetch(1);
239 } else {
240 options.set_clampIndirectArrayBounds(1);
244 }
245
246 match validator.compile(&[&source.str()], options) {
247 Ok(()) => {
248 let translated_source = validator.object_code();
249 debug!("Shader translated: {}", translated_source);
250 self.upcast()
254 .send_command(WebGLCommand::CompileShader(self.id(), translated_source));
255 self.compilation_status
256 .set(ShaderCompilationStatus::Succeeded);
257 },
258 Err(error) => {
259 self.compilation_status.set(ShaderCompilationStatus::Failed);
260 debug!("Shader {} compilation failed: {}", self.id(), error);
261 },
262 }
263
264 *self.info_log.borrow_mut() = validator.info_log().into();
265
266 Ok(())
267 }
268
269 pub(crate) fn mark_for_deletion(&self, operation_fallibility: Operation) {
273 self.droppable.mark_for_deletion(operation_fallibility);
274 }
275
276 pub(crate) fn is_marked_for_deletion(&self) -> bool {
277 self.droppable.marked_for_deletion.get()
278 }
279
280 pub(crate) fn is_deleted(&self) -> bool {
281 self.droppable.marked_for_deletion.get() && !self.is_attached()
282 }
283
284 pub(crate) fn is_attached(&self) -> bool {
285 self.attached_counter.get() > 0
286 }
287
288 pub(crate) fn increment_attached_counter(&self) {
289 self.attached_counter.set(self.attached_counter.get() + 1);
290 }
291
292 pub(crate) fn decrement_attached_counter(&self) {
293 assert!(self.attached_counter.get() > 0);
294 self.attached_counter.set(self.attached_counter.get() - 1);
295 }
296
297 pub(crate) fn info_log(&self) -> DOMString {
299 self.info_log.borrow().clone()
300 }
301
302 pub(crate) fn source(&self) -> DOMString {
304 self.source.borrow().clone()
305 }
306
307 pub(crate) fn set_source(&self, source: DOMString) {
309 *self.source.borrow_mut() = source;
310 }
311
312 pub(crate) fn successfully_compiled(&self) -> bool {
313 self.compilation_status.get() == ShaderCompilationStatus::Succeeded
314 }
315}