1 | /**************************************************************************/ |
2 | /* canvas_item_material.cpp */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
10 | /* */ |
11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
12 | /* a copy of this software and associated documentation files (the */ |
13 | /* "Software"), to deal in the Software without restriction, including */ |
14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
17 | /* the following conditions: */ |
18 | /* */ |
19 | /* The above copyright notice and this permission notice shall be */ |
20 | /* included in all copies or substantial portions of the Software. */ |
21 | /* */ |
22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
29 | /**************************************************************************/ |
30 | |
31 | #include "canvas_item_material.h" |
32 | |
33 | #include "core/version.h" |
34 | |
35 | Mutex CanvasItemMaterial::material_mutex; |
36 | SelfList<CanvasItemMaterial>::List *CanvasItemMaterial::dirty_materials = nullptr; |
37 | HashMap<CanvasItemMaterial::MaterialKey, CanvasItemMaterial::ShaderData, CanvasItemMaterial::MaterialKey> CanvasItemMaterial::shader_map; |
38 | CanvasItemMaterial::ShaderNames *CanvasItemMaterial::shader_names = nullptr; |
39 | |
40 | void CanvasItemMaterial::init_shaders() { |
41 | dirty_materials = memnew(SelfList<CanvasItemMaterial>::List); |
42 | |
43 | shader_names = memnew(ShaderNames); |
44 | |
45 | shader_names->particles_anim_h_frames = "particles_anim_h_frames" ; |
46 | shader_names->particles_anim_v_frames = "particles_anim_v_frames" ; |
47 | shader_names->particles_anim_loop = "particles_anim_loop" ; |
48 | } |
49 | |
50 | void CanvasItemMaterial::finish_shaders() { |
51 | memdelete(dirty_materials); |
52 | memdelete(shader_names); |
53 | dirty_materials = nullptr; |
54 | } |
55 | |
56 | void CanvasItemMaterial::_update_shader() { |
57 | dirty_materials->remove(&element); |
58 | |
59 | MaterialKey mk = _compute_key(); |
60 | if (mk.key == current_key.key) { |
61 | return; //no update required in the end |
62 | } |
63 | |
64 | if (shader_map.has(current_key)) { |
65 | shader_map[current_key].users--; |
66 | if (shader_map[current_key].users == 0) { |
67 | //deallocate shader, as it's no longer in use |
68 | RS::get_singleton()->free(shader_map[current_key].shader); |
69 | shader_map.erase(current_key); |
70 | } |
71 | } |
72 | |
73 | current_key = mk; |
74 | |
75 | if (shader_map.has(mk)) { |
76 | RS::get_singleton()->material_set_shader(_get_material(), shader_map[mk].shader); |
77 | shader_map[mk].users++; |
78 | return; |
79 | } |
80 | |
81 | //must create a shader! |
82 | |
83 | // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). |
84 | String code = "// NOTE: Shader automatically converted from " VERSION_NAME " " VERSION_FULL_CONFIG "'s CanvasItemMaterial.\n\n" ; |
85 | |
86 | code += "shader_type canvas_item;\nrender_mode " ; |
87 | switch (blend_mode) { |
88 | case BLEND_MODE_MIX: |
89 | code += "blend_mix" ; |
90 | break; |
91 | case BLEND_MODE_ADD: |
92 | code += "blend_add" ; |
93 | break; |
94 | case BLEND_MODE_SUB: |
95 | code += "blend_sub" ; |
96 | break; |
97 | case BLEND_MODE_MUL: |
98 | code += "blend_mul" ; |
99 | break; |
100 | case BLEND_MODE_PREMULT_ALPHA: |
101 | code += "blend_premul_alpha" ; |
102 | break; |
103 | case BLEND_MODE_DISABLED: |
104 | code += "blend_disabled" ; |
105 | break; |
106 | } |
107 | |
108 | switch (light_mode) { |
109 | case LIGHT_MODE_NORMAL: |
110 | break; |
111 | case LIGHT_MODE_UNSHADED: |
112 | code += ",unshaded" ; |
113 | break; |
114 | case LIGHT_MODE_LIGHT_ONLY: |
115 | code += ",light_only" ; |
116 | break; |
117 | } |
118 | |
119 | code += ";\n" ; |
120 | |
121 | if (particles_animation) { |
122 | code += "uniform int particles_anim_h_frames;\n" ; |
123 | code += "uniform int particles_anim_v_frames;\n" ; |
124 | code += "uniform bool particles_anim_loop;\n\n" ; |
125 | |
126 | code += "void vertex() {\n" ; |
127 | code += " float h_frames = float(particles_anim_h_frames);\n" ; |
128 | code += " float v_frames = float(particles_anim_v_frames);\n" ; |
129 | code += " VERTEX.xy /= vec2(h_frames, v_frames);\n" ; |
130 | code += " float particle_total_frames = float(particles_anim_h_frames * particles_anim_v_frames);\n" ; |
131 | code += " float particle_frame = floor(INSTANCE_CUSTOM.z * float(particle_total_frames));\n" ; |
132 | code += " if (!particles_anim_loop) {\n" ; |
133 | code += " particle_frame = clamp(particle_frame, 0.0, particle_total_frames - 1.0);\n" ; |
134 | code += " } else {\n" ; |
135 | code += " particle_frame = mod(particle_frame, particle_total_frames);\n" ; |
136 | code += " }" ; |
137 | code += " UV /= vec2(h_frames, v_frames);\n" ; |
138 | code += " UV += vec2(mod(particle_frame, h_frames) / h_frames, floor((particle_frame + 0.5) / h_frames) / v_frames);\n" ; |
139 | code += "}\n" ; |
140 | } |
141 | |
142 | ShaderData shader_data; |
143 | shader_data.shader = RS::get_singleton()->shader_create(); |
144 | shader_data.users = 1; |
145 | |
146 | RS::get_singleton()->shader_set_code(shader_data.shader, code); |
147 | |
148 | shader_map[mk] = shader_data; |
149 | |
150 | RS::get_singleton()->material_set_shader(_get_material(), shader_data.shader); |
151 | } |
152 | |
153 | void CanvasItemMaterial::flush_changes() { |
154 | MutexLock lock(material_mutex); |
155 | |
156 | while (dirty_materials->first()) { |
157 | dirty_materials->first()->self()->_update_shader(); |
158 | } |
159 | } |
160 | |
161 | void CanvasItemMaterial::_queue_shader_change() { |
162 | MutexLock lock(material_mutex); |
163 | |
164 | if (_is_initialized() && !element.in_list()) { |
165 | dirty_materials->add(&element); |
166 | } |
167 | } |
168 | |
169 | bool CanvasItemMaterial::_is_shader_dirty() const { |
170 | MutexLock lock(material_mutex); |
171 | |
172 | return element.in_list(); |
173 | } |
174 | |
175 | void CanvasItemMaterial::set_blend_mode(BlendMode p_blend_mode) { |
176 | blend_mode = p_blend_mode; |
177 | _queue_shader_change(); |
178 | } |
179 | |
180 | CanvasItemMaterial::BlendMode CanvasItemMaterial::get_blend_mode() const { |
181 | return blend_mode; |
182 | } |
183 | |
184 | void CanvasItemMaterial::set_light_mode(LightMode p_light_mode) { |
185 | light_mode = p_light_mode; |
186 | _queue_shader_change(); |
187 | } |
188 | |
189 | CanvasItemMaterial::LightMode CanvasItemMaterial::get_light_mode() const { |
190 | return light_mode; |
191 | } |
192 | |
193 | void CanvasItemMaterial::set_particles_animation(bool p_particles_anim) { |
194 | particles_animation = p_particles_anim; |
195 | _queue_shader_change(); |
196 | notify_property_list_changed(); |
197 | } |
198 | |
199 | bool CanvasItemMaterial::get_particles_animation() const { |
200 | return particles_animation; |
201 | } |
202 | |
203 | void CanvasItemMaterial::set_particles_anim_h_frames(int p_frames) { |
204 | particles_anim_h_frames = p_frames; |
205 | RS::get_singleton()->material_set_param(_get_material(), shader_names->particles_anim_h_frames, p_frames); |
206 | } |
207 | |
208 | int CanvasItemMaterial::get_particles_anim_h_frames() const { |
209 | return particles_anim_h_frames; |
210 | } |
211 | |
212 | void CanvasItemMaterial::set_particles_anim_v_frames(int p_frames) { |
213 | particles_anim_v_frames = p_frames; |
214 | RS::get_singleton()->material_set_param(_get_material(), shader_names->particles_anim_v_frames, p_frames); |
215 | } |
216 | |
217 | int CanvasItemMaterial::get_particles_anim_v_frames() const { |
218 | return particles_anim_v_frames; |
219 | } |
220 | |
221 | void CanvasItemMaterial::set_particles_anim_loop(bool p_loop) { |
222 | particles_anim_loop = p_loop; |
223 | RS::get_singleton()->material_set_param(_get_material(), shader_names->particles_anim_loop, particles_anim_loop); |
224 | } |
225 | |
226 | bool CanvasItemMaterial::get_particles_anim_loop() const { |
227 | return particles_anim_loop; |
228 | } |
229 | |
230 | void CanvasItemMaterial::_validate_property(PropertyInfo &p_property) const { |
231 | if (p_property.name.begins_with("particles_anim_" ) && !particles_animation) { |
232 | p_property.usage = PROPERTY_USAGE_NONE; |
233 | } |
234 | } |
235 | |
236 | RID CanvasItemMaterial::get_shader_rid() const { |
237 | ERR_FAIL_COND_V(!shader_map.has(current_key), RID()); |
238 | return shader_map[current_key].shader; |
239 | } |
240 | |
241 | Shader::Mode CanvasItemMaterial::get_shader_mode() const { |
242 | return Shader::MODE_CANVAS_ITEM; |
243 | } |
244 | |
245 | void CanvasItemMaterial::_bind_methods() { |
246 | ClassDB::bind_method(D_METHOD("set_blend_mode" , "blend_mode" ), &CanvasItemMaterial::set_blend_mode); |
247 | ClassDB::bind_method(D_METHOD("get_blend_mode" ), &CanvasItemMaterial::get_blend_mode); |
248 | |
249 | ClassDB::bind_method(D_METHOD("set_light_mode" , "light_mode" ), &CanvasItemMaterial::set_light_mode); |
250 | ClassDB::bind_method(D_METHOD("get_light_mode" ), &CanvasItemMaterial::get_light_mode); |
251 | |
252 | ClassDB::bind_method(D_METHOD("set_particles_animation" , "particles_anim" ), &CanvasItemMaterial::set_particles_animation); |
253 | ClassDB::bind_method(D_METHOD("get_particles_animation" ), &CanvasItemMaterial::get_particles_animation); |
254 | |
255 | ClassDB::bind_method(D_METHOD("set_particles_anim_h_frames" , "frames" ), &CanvasItemMaterial::set_particles_anim_h_frames); |
256 | ClassDB::bind_method(D_METHOD("get_particles_anim_h_frames" ), &CanvasItemMaterial::get_particles_anim_h_frames); |
257 | |
258 | ClassDB::bind_method(D_METHOD("set_particles_anim_v_frames" , "frames" ), &CanvasItemMaterial::set_particles_anim_v_frames); |
259 | ClassDB::bind_method(D_METHOD("get_particles_anim_v_frames" ), &CanvasItemMaterial::get_particles_anim_v_frames); |
260 | |
261 | ClassDB::bind_method(D_METHOD("set_particles_anim_loop" , "loop" ), &CanvasItemMaterial::set_particles_anim_loop); |
262 | ClassDB::bind_method(D_METHOD("get_particles_anim_loop" ), &CanvasItemMaterial::get_particles_anim_loop); |
263 | |
264 | ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode" , PROPERTY_HINT_ENUM, "Mix,Add,Subtract,Multiply,Premultiplied Alpha" ), "set_blend_mode" , "get_blend_mode" ); |
265 | ADD_PROPERTY(PropertyInfo(Variant::INT, "light_mode" , PROPERTY_HINT_ENUM, "Normal,Unshaded,Light Only" ), "set_light_mode" , "get_light_mode" ); |
266 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "particles_animation" ), "set_particles_animation" , "get_particles_animation" ); |
267 | |
268 | ADD_PROPERTY(PropertyInfo(Variant::INT, "particles_anim_h_frames" , PROPERTY_HINT_RANGE, "1,128,1" ), "set_particles_anim_h_frames" , "get_particles_anim_h_frames" ); |
269 | ADD_PROPERTY(PropertyInfo(Variant::INT, "particles_anim_v_frames" , PROPERTY_HINT_RANGE, "1,128,1" ), "set_particles_anim_v_frames" , "get_particles_anim_v_frames" ); |
270 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "particles_anim_loop" ), "set_particles_anim_loop" , "get_particles_anim_loop" ); |
271 | |
272 | BIND_ENUM_CONSTANT(BLEND_MODE_MIX); |
273 | BIND_ENUM_CONSTANT(BLEND_MODE_ADD); |
274 | BIND_ENUM_CONSTANT(BLEND_MODE_SUB); |
275 | BIND_ENUM_CONSTANT(BLEND_MODE_MUL); |
276 | BIND_ENUM_CONSTANT(BLEND_MODE_PREMULT_ALPHA); |
277 | |
278 | BIND_ENUM_CONSTANT(LIGHT_MODE_NORMAL); |
279 | BIND_ENUM_CONSTANT(LIGHT_MODE_UNSHADED); |
280 | BIND_ENUM_CONSTANT(LIGHT_MODE_LIGHT_ONLY); |
281 | } |
282 | |
283 | CanvasItemMaterial::CanvasItemMaterial() : |
284 | element(this) { |
285 | set_particles_anim_h_frames(1); |
286 | set_particles_anim_v_frames(1); |
287 | set_particles_anim_loop(false); |
288 | |
289 | current_key.invalid_key = 1; |
290 | |
291 | _mark_initialized(callable_mp(this, &CanvasItemMaterial::_queue_shader_change)); |
292 | } |
293 | |
294 | CanvasItemMaterial::~CanvasItemMaterial() { |
295 | MutexLock lock(material_mutex); |
296 | |
297 | ERR_FAIL_NULL(RenderingServer::get_singleton()); |
298 | |
299 | if (shader_map.has(current_key)) { |
300 | shader_map[current_key].users--; |
301 | if (shader_map[current_key].users == 0) { |
302 | //deallocate shader, as it's no longer in use |
303 | RS::get_singleton()->free(shader_map[current_key].shader); |
304 | shader_map.erase(current_key); |
305 | } |
306 | |
307 | RS::get_singleton()->material_set_shader(_get_material(), RID()); |
308 | } |
309 | } |
310 | |