diff options
author | Juan Linietsky <reduzio@gmail.com> | 2015-03-02 00:54:10 -0300 |
---|---|---|
committer | Juan Linietsky <reduzio@gmail.com> | 2015-03-02 00:54:43 -0300 |
commit | a1f715a4da71fbc2b7d6fad68624bf8b22c6da17 (patch) | |
tree | 5cd305c8f8858504f0b5efb804b2c291b1dc5089 /drivers | |
parent | 0e732637d07d908daf64526f222e71fea402f3db (diff) |
support for 2D shadow casters
Added support for 2D shadow casters.
*DANGER* Shaders in CanvasItem CHANGED, if you are using shader in a
CanvasItem and pull this, you will lose them. Shaders now work through a
2D material system similar to 3D. If you don't want to lose the 2D
shader code, save the shader as a .shd, then create a material in
CanvasItem and re-assign the shader.
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/SCsub | 1 | ||||
-rw-r--r-- | drivers/gles2/rasterizer_gles2.cpp | 598 | ||||
-rw-r--r-- | drivers/gles2/rasterizer_gles2.h | 62 | ||||
-rw-r--r-- | drivers/gles2/shaders/SCsub | 1 | ||||
-rw-r--r-- | drivers/gles2/shaders/canvas.glsl | 123 | ||||
-rw-r--r-- | drivers/gles2/shaders/canvas_shadow.glsl | 62 | ||||
-rw-r--r-- | drivers/windows/file_access_windows.cpp | 20 |
7 files changed, 823 insertions, 44 deletions
diff --git a/drivers/SCsub b/drivers/SCsub index 46334468ba..a1a2191cbc 100644 --- a/drivers/SCsub +++ b/drivers/SCsub @@ -10,7 +10,6 @@ SConscript('alsa/SCsub'); SConscript('pulseaudio/SCsub');
SConscript('windows/SCsub');
SConscript('gles2/SCsub');
-SConscript('gles1/SCsub');
SConscript('gl_context/SCsub');
SConscript('openssl/SCsub');
diff --git a/drivers/gles2/rasterizer_gles2.cpp b/drivers/gles2/rasterizer_gles2.cpp index aba0b5ca90..aa7bad0b8f 100644 --- a/drivers/gles2/rasterizer_gles2.cpp +++ b/drivers/gles2/rasterizer_gles2.cpp @@ -971,7 +971,7 @@ void RasterizerGLES2::texture_set_data(RID p_texture,const Image& p_image,VS::Cu - if ((!texture->flags&VS::TEXTURE_FLAG_VIDEO_SURFACE) && img.detect_alpha()==Image::ALPHA_BLEND) { + if (!(texture->flags&VS::TEXTURE_FLAG_VIDEO_SURFACE) && img.detect_alpha()==Image::ALPHA_BLEND) { texture->has_alpha=true; } @@ -2525,7 +2525,7 @@ Error RasterizerGLES2::_surface_set_arrays(Surface *p_surface, uint8_t *p_mem,ui void RasterizerGLES2::mesh_add_custom_surface(RID p_mesh,const Variant& p_dat) { ERR_EXPLAIN("OpenGL Rasterizer does not support custom surfaces. Running on wrong platform?"); - ERR_FAIL_V(); + ERR_FAIL(); } Array RasterizerGLES2::mesh_get_surface_arrays(RID p_mesh,int p_surface) const { @@ -4116,6 +4116,10 @@ void RasterizerGLES2::begin_frame() { shadow_filter=ShadowFilterTechnique(int(Globals::get_singleton()->get("rasterizer/shadow_filter"))); #endif + canvas_shader.set_conditional(CanvasShaderGLES2::SHADOW_PCF5,shadow_filter==SHADOW_FILTER_PCF5); + canvas_shader.set_conditional(CanvasShaderGLES2::SHADOW_PCF13,shadow_filter==SHADOW_FILTER_PCF13); + canvas_shader.set_conditional(CanvasShaderGLES2::SHADOW_ESM,shadow_filter==SHADOW_FILTER_ESM); + window_size = Size2( OS::get_singleton()->get_video_mode().width, OS::get_singleton()->get_video_mode().height ); double time = (OS::get_singleton()->get_ticks_usec()/1000); // get msec @@ -7874,7 +7878,8 @@ void RasterizerGLES2::canvas_begin() { canvas_blend_mode=VS::MATERIAL_BLEND_MODE_MIX; canvas_texscreen_used=false; uses_texpixel_size=false; - canvas_last_shader=RID(); + + canvas_last_material=NULL; } @@ -8364,6 +8369,476 @@ void RasterizerGLES2::canvas_set_transform(const Matrix32& p_transform) { //canvas_transform = Variant(p_transform); } +RID RasterizerGLES2::canvas_light_occluder_create() { + + CanvasOccluder *co = memnew( CanvasOccluder ); + co->index_id=0; + co->vertex_id=0; + co->len=0; + + return canvas_occluder_owner.make_rid(co); +} + +void RasterizerGLES2::canvas_light_occluder_set_polylines(RID p_occluder, const DVector<Vector2>& p_lines) { + + CanvasOccluder *co = canvas_occluder_owner.get(p_occluder); + ERR_FAIL_COND(!co); + + co->lines=p_lines; + + if (p_lines.size()!=co->len) { + + if (co->index_id) + glDeleteBuffers(1,&co->index_id); + if (co->vertex_id) + glDeleteBuffers(1,&co->vertex_id); + + co->index_id=0; + co->vertex_id=0; + co->len=0; + + } + + if (p_lines.size()) { + + + + DVector<float> geometry; + DVector<uint16_t> indices; + int lc = p_lines.size(); + + geometry.resize(lc*6); + indices.resize(lc*3); + + DVector<float>::Write vw=geometry.write(); + DVector<uint16_t>::Write iw=indices.write(); + + + DVector<Vector2>::Read lr=p_lines.read(); + + const int POLY_HEIGHT = 16384; + + for(int i=0;i<lc/2;i++) { + + vw[i*12+0]=lr[i*2+0].x; + vw[i*12+1]=lr[i*2+0].y; + vw[i*12+2]=POLY_HEIGHT; + + vw[i*12+3]=lr[i*2+1].x; + vw[i*12+4]=lr[i*2+1].y; + vw[i*12+5]=POLY_HEIGHT; + + vw[i*12+6]=lr[i*2+1].x; + vw[i*12+7]=lr[i*2+1].y; + vw[i*12+8]=-POLY_HEIGHT; + + vw[i*12+9]=lr[i*2+0].x; + vw[i*12+10]=lr[i*2+0].y; + vw[i*12+11]=-POLY_HEIGHT; + + iw[i*6+0]=i*4+0; + iw[i*6+1]=i*4+1; + iw[i*6+2]=i*4+2; + + iw[i*6+3]=i*4+2; + iw[i*6+4]=i*4+3; + iw[i*6+5]=i*4+0; + + } + + //if same buffer len is being set, just use BufferSubData to avoid a pipeline flush + + + if (!co->vertex_id) { + glGenBuffers(1,&co->vertex_id); + glBindBuffer(GL_ARRAY_BUFFER,co->vertex_id); + glBufferData(GL_ARRAY_BUFFER,lc*6*sizeof(real_t),vw.ptr(),GL_STATIC_DRAW); + } else { + + glBindBuffer(GL_ARRAY_BUFFER,co->vertex_id); + glBufferSubData(GL_ARRAY_BUFFER,0,lc*6*sizeof(real_t),vw.ptr()); + + } + + glBindBuffer(GL_ARRAY_BUFFER,0); //unbind + + if (!co->index_id) { + + glGenBuffers(1,&co->index_id); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,co->index_id); + glBufferData(GL_ELEMENT_ARRAY_BUFFER,lc*3*sizeof(uint16_t),iw.ptr(),GL_STATIC_DRAW); + } else { + + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,co->index_id); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER,0,lc*3*sizeof(uint16_t),iw.ptr()); + } + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0); //unbind + + co->len=lc; + + } + + + +} + +RID RasterizerGLES2::canvas_light_shadow_buffer_create(int p_width) { + + CanvasLightShadow *cls = memnew( CanvasLightShadow ); + if (p_width>max_texture_size) + p_width=max_texture_size; + + cls->size=p_width; + glActiveTexture(GL_TEXTURE0); + + glGenFramebuffers(1, &cls->fbo); + glBindFramebuffer(GL_FRAMEBUFFER, cls->fbo); + + // Create a render buffer + glGenRenderbuffers(1, &cls->rbo); + glBindRenderbuffer(GL_RENDERBUFFER, cls->rbo); + + // Create a texture for storing the depth + glGenTextures(1, &cls->depth); + glBindTexture(GL_TEXTURE_2D, cls->depth); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Remove artifact on the edges of the shadowmap + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + cls->height=16; + + //print_line("ERROR? "+itos(glGetError())); + if ( read_depth_supported ) { + + // We'll use a depth texture to store the depths in the shadow map + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, cls->size, cls->height, 0, + GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL); + +#ifdef GLEW_ENABLED + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); +#endif + + // Attach the depth texture to FBO depth attachment point + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, + GL_TEXTURE_2D, cls->depth, 0); + +#ifdef GLEW_ENABLED + glDrawBuffer(GL_NONE); +#endif + } else { + // We'll use a RGBA texture into which we pack the depth info + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, cls->size, cls->height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, NULL); + + // Attach the RGBA texture to FBO color attachment point + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, cls->depth, 0); + + // Allocate 16-bit depth buffer + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, cls->size, cls->height); + + // Attach the render buffer as depth buffer - will be ignored + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, + GL_RENDERBUFFER, cls->rbo); + + + } + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + //printf("errnum: %x\n",status); +#ifdef GLEW_ENABLED + if (read_depth_supported) { + glDrawBuffer(GL_BACK); + } +#endif + glBindFramebuffer(GL_FRAMEBUFFER, base_framebuffer); + DEBUG_TEST_ERROR("2D Shadow Buffer Init"); + ERR_FAIL_COND_V( status != GL_FRAMEBUFFER_COMPLETE, RID() ); + +#ifdef GLEW_ENABLED + if (read_depth_supported) { + glDrawBuffer(GL_BACK); + } +#endif + + return canvas_light_shadow_owner.make_rid(cls); +} + +void RasterizerGLES2::canvas_light_shadow_buffer_update(RID p_buffer, const Matrix32& p_light_xform, int p_light_mask,float p_near, float p_far, CanvasLightOccluderInstance* p_occluders, CameraMatrix *p_xform_cache) { + + CanvasLightShadow *cls = canvas_light_shadow_owner.get(p_buffer); + ERR_FAIL_COND(!cls); + + + glDisable(GL_BLEND); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_DITHER); + glDisable(GL_CULL_FACE); + glDepthFunc(GL_LEQUAL); + glEnable(GL_DEPTH_TEST); + glDepthMask(true); + + glBindFramebuffer(GL_FRAMEBUFFER, cls->fbo); + + if (!use_rgba_shadowmaps) + glColorMask(0, 0, 0, 0); + + glEnableVertexAttribArray(VS::ARRAY_VERTEX); + canvas_shadow_shader.bind(); + + const int vp_height = 10; + + glViewport(0, 0, cls->size,cls->height); + _glClearDepth(1.0f); + glClearColor(1,1,1,1); + glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); + + VS::CanvasOccluderPolygonCullMode cull=VS::CANVAS_OCCLUDER_POLYGON_CULL_DISABLED; + + + for(int i=0;i<4;i++) { + + //make sure it remains orthogonal, makes easy to read angle later + + Transform light; + light.origin[0]=p_light_xform[2][0]; + light.origin[1]=p_light_xform[2][1]; + light.basis[0][0]=p_light_xform[0][0]; + light.basis[0][1]=p_light_xform[1][0]; + light.basis[1][0]=p_light_xform[0][1]; + light.basis[1][1]=p_light_xform[1][1]; + + //light.basis.scale(Vector3(to_light.elements[0].length(),to_light.elements[1].length(),1)); + + /// p_near=1; + CameraMatrix projection; + { + real_t fov = 90; + real_t near = p_near; + real_t far = p_far; + real_t aspect = 1.0; + + real_t ymax = near * Math::tan( Math::deg2rad( fov * 0.5 ) ); + real_t ymin = - ymax; + real_t xmin = ymin * aspect; + real_t xmax = ymax * aspect; + + projection.set_frustum( xmin, xmax, ymin, ymax, near, far ); + } + + Vector3 cam_target=Matrix3(Vector3(0,0,Math_PI*2*(i/4.0))).xform(Vector3(0,1,0)); + projection = projection * CameraMatrix(Transform().looking_at(cam_target,Vector3(0,0,-1)).affine_inverse()); + + //print_line("near: "+rtos(p_near)); + //print_line("far: "+rtos(p_far)); + //projection.set_perspective(60,size/float(vp_height),p_near,p_far); + + // CameraMatrix light_mat = projection * CameraMatrix(camera); + + canvas_shadow_shader.set_uniform(CanvasShadowShaderGLES2::PROJECTION_MATRIX,projection); + canvas_shadow_shader.set_uniform(CanvasShadowShaderGLES2::LIGHT_MATRIX,light); + + if (i==0) + *p_xform_cache=projection; + + glViewport(0, (cls->height/4)*i, cls->size,cls->height/4); + + CanvasLightOccluderInstance *instance=p_occluders; + + while(instance) { + + CanvasOccluder *cc = canvas_occluder_owner.get(instance->polygon_buffer); + if (!cc || cc->len==0 || !(p_light_mask&instance->light_mask)) { + + instance=instance->next; + continue; + } + + canvas_shadow_shader.set_uniform(CanvasShadowShaderGLES2::WORLD_MATRIX,instance->xform_cache); + if (cull!=instance->cull_cache) { + + cull=instance->cull_cache; + switch(cull) { + case VS::CANVAS_OCCLUDER_POLYGON_CULL_DISABLED: { + + glDisable(GL_CULL_FACE); + + } break; + case VS::CANVAS_OCCLUDER_POLYGON_CULL_CLOCKWISE: { + + glEnable(GL_CULL_FACE); + glCullFace(GL_FRONT); + } break; + case VS::CANVAS_OCCLUDER_POLYGON_CULL_COUNTER_CLOCKWISE: { + + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + + } break; + } + } +/* + if (i==0) { + for(int i=0;i<cc->lines.size();i++) { + Vector2 p = instance->xform_cache.xform(cc->lines.get(i)); + Plane pp(Vector3(p.x,p.y,0),1); + pp.normal = light.xform(pp.normal); + pp = projection.xform4(pp); + print_line(itos(i)+": "+pp.normal/pp.d); + //pp=light_mat.xform4(pp); + //print_line(itos(i)+": "+pp.normal/pp.d); + } + } +*/ + glBindBuffer(GL_ARRAY_BUFFER,cc->vertex_id); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,cc->index_id); + glVertexAttribPointer(VS::ARRAY_VERTEX, 3, GL_FLOAT, false, 0, 0); + glDrawElements(GL_TRIANGLES,cc->len*3,GL_UNSIGNED_SHORT,0); + + + instance=instance->next; + } + + + } + + glDisableVertexAttribArray(VS::ARRAY_VERTEX); + glBindBuffer(GL_ARRAY_BUFFER,0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0); + + if (shadow_filter==SHADOW_FILTER_ESM) { + //blur the buffer +#if 0 + //this is ignord, it did not make any difference.. + if (read_depth_supported) { + glDepthFunc(GL_ALWAYS); + } else { + glDisable(GL_DEPTH_TEST); + glDepthMask(false); + } + glDisable(GL_CULL_FACE); + glViewport(0, 0, cls->size,cls->height); + + int passes=1; + CanvasLightShadow *blur = canvas_light_shadow_owner.get(canvas_shadow_blur); + + copy_shader.set_conditional(CopyShaderGLES2::SHADOW_BLUR_H_PASS,true); + copy_shader.bind(); + copy_shader.set_uniform(CopyShaderGLES2::PIXEL_SCALE,1); + copy_shader.set_uniform(CopyShaderGLES2::BLUR_MAGNITUDE,1); + glUniform1i(copy_shader.get_uniform_location(CopyShaderGLES2::SOURCE),0); + + for(int i=0;i<passes;i++) { + + glBindFramebuffer(GL_FRAMEBUFFER, blur->fbo); + glActiveTexture(GL_TEXTURE0); + + if (read_depth_supported) + glBindTexture(GL_TEXTURE_2D,cls->depth); + else + glBindTexture(GL_TEXTURE_2D,cls->rgba); + + + { + Vector2 src_sb_uv[4]={ + Vector2( 0, 1), + Vector2( 1, 1), + Vector2( 1, 0), + Vector2( 0, 0) + }; + static const Vector2 dst_pos[4]={ + Vector2(-1, 1), + Vector2( 1, 1), + Vector2( 1,-1), + Vector2(-1,-1) + }; + + + + copy_shader.set_uniform(CopyShaderGLES2::PIXEL_SIZE,Vector2(1.0,1.0)/cls->size); + _draw_gui_primitive(4,dst_pos,NULL,src_sb_uv); + } + + glActiveTexture(GL_TEXTURE0); + if (read_depth_supported) + glBindTexture(GL_TEXTURE_2D,blur->depth); + else + glBindTexture(GL_TEXTURE_2D,blur->rgba); + + glBindFramebuffer(GL_FRAMEBUFFER, cls->fbo); + + { + float hlimit = float(cls->size) / blur->size; + //hlimit*=2.0; + Vector2 src_sb_uv[4]={ + Vector2( 0, 1), + Vector2( hlimit, 1), + Vector2( hlimit, 0), + Vector2( 0, 0) + }; + static const Vector2 dst_pos[4]={ + Vector2(-1, 1), + Vector2( 1, 1), + Vector2( 1,-1), + Vector2(-1,-1) + }; + + + copy_shader.set_uniform(CopyShaderGLES2::PIXEL_SIZE,Vector2(1.0,1.0)/blur->size); + _draw_gui_primitive(4,dst_pos,NULL,src_sb_uv); + } + + } + copy_shader.set_conditional(CopyShaderGLES2::SHADOW_BLUR_H_PASS,false); + glDepthFunc(GL_LEQUAL); +#endif + } + + glBindFramebuffer(GL_FRAMEBUFFER, current_rt?current_rt->fbo:base_framebuffer); + glColorMask(1, 1, 1, 1); + + + +} + + +void RasterizerGLES2::canvas_debug_viewport_shadows(CanvasLight* p_lights_with_shadow) { + + CanvasLight* light=p_lights_with_shadow; + + canvas_begin(); //reset + + int h = 10; + int w = viewport.width; + int ofs = h; + while(light) { + + if (light->shadow_buffer.is_valid()) { + + CanvasLightShadow * sb = canvas_light_shadow_owner.get(light->shadow_buffer); + if (sb) { + glActiveTexture(GL_TEXTURE0); + if (read_depth_supported) + glBindTexture(GL_TEXTURE_2D,sb->depth); + else + glBindTexture(GL_TEXTURE_2D,sb->rgba); + _draw_textured_quad(Rect2(h,ofs,w-h*2,h),Rect2(0,0,sb->size,10),Size2(sb->size,10),false,false); + ofs+=h*2; + + } + } + + light=light->shadows_next_ptr; + } + +} + void RasterizerGLES2::_canvas_normal_set_flip(const Vector2& p_flip) { if (p_flip==normal_flip) @@ -8508,14 +8983,14 @@ void RasterizerGLES2::_canvas_item_render_commands(CanvasItem *p_item,CanvasItem } -void RasterizerGLES2::_canvas_item_setup_shader_params(CanvasItem *shader_owner,Shader* shader) { +void RasterizerGLES2::_canvas_item_setup_shader_params(CanvasItemMaterial *material,Shader* shader) { if (canvas_shader.bind()) rebind_texpixel_size=true; - if (shader_owner->shader_version!=shader->version) { + if (material->shader_version!=shader->version) { //todo optimize uniforms - shader_owner->shader_version=shader->version; + material->shader_version=shader->version; } if (shader->has_texscreen && framebuffer.active) { @@ -8558,14 +9033,14 @@ void RasterizerGLES2::_canvas_item_setup_shader_params(CanvasItem *shader_owner, } -void RasterizerGLES2::_canvas_item_setup_shader_uniforms(CanvasItem *shader_owner,Shader* shader) { +void RasterizerGLES2::_canvas_item_setup_shader_uniforms(CanvasItemMaterial *material,Shader* shader) { //this can be optimized.. int tex_id=1; int idx=0; for(Map<StringName,ShaderLanguage::Uniform>::Element *E=shader->uniforms.front();E;E=E->next()) { - Map<StringName,Variant>::Element *F=shader_owner->shader_param.find(E->key()); + Map<StringName,Variant>::Element *F=material->shader_param.find(E->key()); if ((E->get().type==ShaderLanguage::TYPE_TEXTURE || E->get().type==ShaderLanguage::TYPE_CUBEMAP)) { @@ -8623,6 +9098,8 @@ void RasterizerGLES2::canvas_render_items(CanvasItem *p_item_list,int p_z,const canvas_modulate=p_modulate; canvas_shader.set_conditional(CanvasShaderGLES2::USE_MODULATE,canvas_use_modulate); + bool reset_modulate=false; + while(p_item_list) { CanvasItem *ci=p_item_list; @@ -8632,12 +9109,13 @@ void RasterizerGLES2::canvas_render_items(CanvasItem *p_item_list,int p_z,const draw_viewport_func(ci->vp_render->owner,ci->vp_render->udata,ci->vp_render->rect); } memdelete(ci->vp_render); - ci->vp_render=NULL; - canvas_last_shader=RID(); + ci->vp_render=NULL; + canvas_last_material=NULL; canvas_use_modulate=p_modulate!=Color(1,1,1,1); canvas_modulate=p_modulate; canvas_shader.set_conditional(CanvasShaderGLES2::USE_MODULATE,canvas_use_modulate); rebind_shader=true; + reset_modulate=true; } @@ -8660,13 +9138,14 @@ void RasterizerGLES2::canvas_render_items(CanvasItem *p_item_list,int p_z,const //begin rect - CanvasItem *shader_owner = ci->shader_owner?ci->shader_owner:ci; + CanvasItem *material_owner = ci->material_owner?ci->material_owner:ci; + CanvasItemMaterial *material = material_owner->material; - if (shader_owner->shader!=canvas_last_shader || rebind_shader) { + if (material!=canvas_last_material || rebind_shader) { Shader *shader = NULL; - if (shader_owner->shader.is_valid()) { - shader = this->shader_owner.get(shader_owner->shader); + if (material && material->shader.is_valid()) { + shader = shader_owner.get(material->shader); if (shader && !shader->valid) { shader=NULL; } @@ -8676,7 +9155,7 @@ void RasterizerGLES2::canvas_render_items(CanvasItem *p_item_list,int p_z,const if (shader) { canvas_shader.set_custom_shader(shader->custom_code_id); - _canvas_item_setup_shader_params(shader_owner,shader); + _canvas_item_setup_shader_params(material,shader); } else { shader_cache=NULL; canvas_shader.set_custom_shader(0); @@ -8688,16 +9167,26 @@ void RasterizerGLES2::canvas_render_items(CanvasItem *p_item_list,int p_z,const canvas_shader.set_uniform(CanvasShaderGLES2::PROJECTION_MATRIX,canvas_transform); if (canvas_use_modulate) - canvas_shader.set_uniform(CanvasShaderGLES2::MODULATE,canvas_modulate); - canvas_last_shader=shader_owner->shader; + reset_modulate=true; + canvas_last_material=material; rebind_shader=false; } - if (shader_cache) { + if (material && shader_cache) { + + _canvas_item_setup_shader_uniforms(material,shader_cache); + } - _canvas_item_setup_shader_uniforms(shader_owner,shader_cache); + if (material && material->unshaded) { + canvas_shader.set_uniform(CanvasShaderGLES2::MODULATE,Color(1,1,1,1)); + reset_modulate=true; + } else if (reset_modulate) { + canvas_shader.set_uniform(CanvasShaderGLES2::MODULATE,canvas_modulate); + reset_modulate=false; } + + canvas_shader.set_uniform(CanvasShaderGLES2::MODELVIEW_MATRIX,ci->final_transform); canvas_shader.set_uniform(CanvasShaderGLES2::EXTRA_MATRIX,Matrix32()); @@ -8747,7 +9236,7 @@ void RasterizerGLES2::canvas_render_items(CanvasItem *p_item_list,int p_z,const _canvas_item_render_commands<false>(ci,current_clip,reclip); - if (canvas_blend_mode==VS::MATERIAL_BLEND_MODE_MIX && p_light) { + if (canvas_blend_mode==VS::MATERIAL_BLEND_MODE_MIX && p_light && (!material || !material->unshaded)) { CanvasLight *light = p_light; bool light_used=false; @@ -8785,13 +9274,15 @@ void RasterizerGLES2::canvas_render_items(CanvasItem *p_item_list,int p_z,const normal_flip=Vector2(1,1); } + canvas_shader.set_conditional(CanvasShaderGLES2::USE_SHADOWS,light->shadow_buffer.is_valid()); + bool light_rebind = canvas_shader.bind(); if (light_rebind) { - if (shader_owner && shader_cache) { - _canvas_item_setup_shader_params(shader_owner,shader_cache); - _canvas_item_setup_shader_uniforms(shader_owner,shader_cache); + if (material && shader_cache) { + _canvas_item_setup_shader_params(material,shader_cache); + _canvas_item_setup_shader_uniforms(material,shader_cache); } canvas_shader.set_uniform(CanvasShaderGLES2::MODELVIEW_MATRIX,ci->final_transform); @@ -8800,6 +9291,7 @@ void RasterizerGLES2::canvas_render_items(CanvasItem *p_item_list,int p_z,const if (canvas_use_modulate) canvas_shader.set_uniform(CanvasShaderGLES2::MODULATE,canvas_modulate); canvas_shader.set_uniform(CanvasShaderGLES2::NORMAL_FLIP,Vector2(1,1)); + canvas_shader.set_uniform(CanvasShaderGLES2::SHADOWPIXEL_SIZE,1.0/light->shadow_buffer_size); } @@ -8808,6 +9300,23 @@ void RasterizerGLES2::canvas_render_items(CanvasItem *p_item_list,int p_z,const canvas_shader.set_uniform(CanvasShaderGLES2::LIGHT_POS,light->light_shader_pos); canvas_shader.set_uniform(CanvasShaderGLES2::LIGHT_COLOR,light->color); canvas_shader.set_uniform(CanvasShaderGLES2::LIGHT_HEIGHT,light->height); + if (light->shadow_buffer.is_valid()) { + + CanvasLightShadow *cls = canvas_light_shadow_owner.get(light->shadow_buffer); + glActiveTexture(GL_TEXTURE0+max_texture_units-3); + if (read_depth_supported) + glBindTexture(GL_TEXTURE_2D,cls->depth); + else + glBindTexture(GL_TEXTURE_2D,cls->rgba); + + canvas_shader.set_uniform(CanvasShaderGLES2::SHADOW_TEXTURE,max_texture_units-3); + canvas_shader.set_uniform(CanvasShaderGLES2::SHADOW_MATRIX,light->shadow_matrix_cache); + canvas_shader.set_uniform(CanvasShaderGLES2::LIGHT_LOCAL_MATRIX,light->xform_cache.affine_inverse()); + canvas_shader.set_uniform(CanvasShaderGLES2::SHADOW_ESM_MULTIPLIER,light->shadow_esm_mult); + + } + + glActiveTexture(GL_TEXTURE0+max_texture_units-2); canvas_shader.set_uniform(CanvasShaderGLES2::LIGHT_TEXTURE,max_texture_units-2); Texture *t = texture_owner.get(light->texture); @@ -8831,12 +9340,13 @@ void RasterizerGLES2::canvas_render_items(CanvasItem *p_item_list,int p_z,const canvas_shader.set_conditional(CanvasShaderGLES2::USE_LIGHTING,false); canvas_shader.set_conditional(CanvasShaderGLES2::USE_MODULATE,canvas_use_modulate); + canvas_shader.set_conditional(CanvasShaderGLES2::USE_SHADOWS,false); canvas_shader.bind(); - if (shader_owner && shader_cache) { - _canvas_item_setup_shader_params(shader_owner,shader_cache); - _canvas_item_setup_shader_uniforms(shader_owner,shader_cache); + if (material && shader_cache) { + _canvas_item_setup_shader_params(material,shader_cache); + _canvas_item_setup_shader_uniforms(material,shader_cache); } canvas_shader.set_uniform(CanvasShaderGLES2::MODELVIEW_MATRIX,ci->final_transform); @@ -9067,6 +9577,11 @@ bool RasterizerGLES2::is_environment(const RID& p_rid) const { } bool RasterizerGLES2::is_shader(const RID& p_rid) const { + return shader_owner.owns(p_rid); +} + +bool RasterizerGLES2::is_canvas_light_occluder(const RID& p_rid) const { + return false; } @@ -9244,7 +9759,30 @@ void RasterizerGLES2::free(const RID& p_rid) { glDeleteTextures(1,&sampled_light->texture); sampled_light_owner.free(p_rid); memdelete( sampled_light ); + } else if (canvas_occluder_owner.owns(p_rid)) { + + CanvasOccluder *co = canvas_occluder_owner.get(p_rid); + if (co->index_id) + glDeleteBuffers(1,&co->index_id); + if (co->vertex_id) + glDeleteBuffers(1,&co->vertex_id); + + canvas_occluder_owner.free(p_rid); + memdelete(co); + + } else if (canvas_light_shadow_owner.owns(p_rid)) { + + CanvasLightShadow *cls = canvas_light_shadow_owner.get(p_rid); + glDeleteFramebuffers(1,&cls->fbo); + glDeleteRenderbuffers(1,&cls->rbo); + glDeleteTextures(1,&cls->depth); + if (!read_depth_supported) { + glDeleteTextures(1,&cls->rgba); + } + + canvas_light_shadow_owner.free(p_rid); + memdelete(cls); }; } @@ -9779,10 +10317,12 @@ void RasterizerGLES2::init() { material_shader.init(); canvas_shader.init(); copy_shader.init(); + canvas_shadow_shader.init(); #ifdef GLEW_ENABLED material_shader.set_conditional(MaterialShaderGLES2::USE_GLES_OVER_GL,true); canvas_shader.set_conditional(CanvasShaderGLES2::USE_GLES_OVER_GL,true); + canvas_shadow_shader.set_conditional(CanvasShadowShaderGLES2::USE_GLES_OVER_GL,true); copy_shader.set_conditional(CopyShaderGLES2::USE_GLES_OVER_GL,true); #endif @@ -9923,8 +10463,11 @@ void RasterizerGLES2::init() { glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &max_texture_units); + glGetIntegerv(GL_MAX_TEXTURE_SIZE,&max_texture_size); //read_depth_supported=false; + canvas_shadow_blur = canvas_light_shadow_buffer_create(max_texture_size); + { //shadowmaps OS::VideoMode vm=OS::get_singleton()->get_video_mode(); @@ -9946,6 +10489,7 @@ void RasterizerGLES2::init() { //material_shader material_shader.set_conditional(MaterialShaderGLES2::USE_DEPTH_SHADOWS,!use_rgba_shadowmaps); + canvas_shadow_shader.set_conditional(CanvasShadowShaderGLES2::USE_DEPTH_SHADOWS,!use_rgba_shadowmaps); } @@ -9978,7 +10522,7 @@ void RasterizerGLES2::init() { void RasterizerGLES2::finish() { - + free(canvas_shadow_blur); } int RasterizerGLES2::get_render_info(VS::RenderInfo p_info) { diff --git a/drivers/gles2/rasterizer_gles2.h b/drivers/gles2/rasterizer_gles2.h index 173ca14180..f09714f50c 100644 --- a/drivers/gles2/rasterizer_gles2.h +++ b/drivers/gles2/rasterizer_gles2.h @@ -51,6 +51,7 @@ #include "drivers/gles2/shaders/material.glsl.h" #include "drivers/gles2/shaders/canvas.glsl.h" +#include "drivers/gles2/shaders/canvas_shadow.glsl.h" #include "drivers/gles2/shaders/blur.glsl.h" #include "drivers/gles2/shaders/copy.glsl.h" #include "drivers/gles2/shader_compiler_gles2.h" @@ -816,6 +817,7 @@ class RasterizerGLES2 : public Rasterizer { bool current_depth_mask; VS::MaterialBlendMode current_blend_mode; bool use_fast_texture_filter; + int max_texture_size; bool fragment_lighting; RID shadow_material; @@ -1160,6 +1162,7 @@ class RasterizerGLES2 : public Rasterizer { void _process_glow_and_bloom(); //void _update_blur_buffer(); + /*********/ /* FRAME */ /*********/ @@ -1178,6 +1181,45 @@ class RasterizerGLES2 : public Rasterizer { } _rinfo; + /*******************/ + /* CANVAS OCCLUDER */ + /*******************/ + + + struct CanvasOccluder { + + GLuint vertex_id; // 0 means, unconfigured + GLuint index_id; // 0 means, unconfigured + DVector<Vector2> lines; + int len; + }; + + RID_Owner<CanvasOccluder> canvas_occluder_owner; + + /***********************/ + /* CANVAS LIGHT SHADOW */ + /***********************/ + + + struct CanvasLightShadow { + + int size; + int height; + GLuint fbo; + GLuint rbo; + GLuint depth; + GLuint rgba; //for older devices + + GLuint blur; + + }; + + RID_Owner<CanvasLightShadow> canvas_light_shadow_owner; + + RID canvas_shadow_blur; + + /* ETC */ + RenderTarget *current_rt; bool current_rt_transparent; bool current_rt_vflip; @@ -1192,7 +1234,7 @@ class RasterizerGLES2 : public Rasterizer { bool uses_texpixel_size; bool rebind_texpixel_size; Transform canvas_transform; - RID canvas_last_shader; + CanvasItemMaterial *canvas_last_material; bool canvas_texscreen_used; Vector2 normal_flip; _FORCE_INLINE_ void _canvas_normal_set_flip(const Vector2& p_flip); @@ -1227,10 +1269,12 @@ class RasterizerGLES2 : public Rasterizer { VS::ScenarioDebugMode current_debug; RID overdraw_material; + mutable MaterialShaderGLES2 material_shader; mutable CanvasShaderGLES2 canvas_shader; BlurShaderGLES2 blur_shader; CopyShaderGLES2 copy_shader; + mutable CanvasShadowShaderGLES2 canvas_shadow_shader; mutable ShaderCompilerGLES2 shader_precompiler; @@ -1254,8 +1298,8 @@ class RasterizerGLES2 : public Rasterizer { template<bool use_normalmap> _FORCE_INLINE_ void _canvas_item_render_commands(CanvasItem *p_item,CanvasItem *current_clip,bool &reclip); - _FORCE_INLINE_ void _canvas_item_setup_shader_params(CanvasItem *shader_owner,Shader* p_shader); - _FORCE_INLINE_ void _canvas_item_setup_shader_uniforms(CanvasItem *shader_owner,Shader* p_shader); + _FORCE_INLINE_ void _canvas_item_setup_shader_params(CanvasItemMaterial *material,Shader* p_shader); + _FORCE_INLINE_ void _canvas_item_setup_shader_uniforms(CanvasItemMaterial *material,Shader* p_shader); public: /* TEXTURE API */ @@ -1572,7 +1616,17 @@ public: virtual void canvas_set_transform(const Matrix32& p_transform); virtual void canvas_render_items(CanvasItem *p_item_list,int p_z,const Color& p_modulate,CanvasLight *p_light); + virtual void canvas_debug_viewport_shadows(CanvasLight* p_lights_with_shadow); + /* CANVAS LIGHT SHADOW */ + + //buffer + virtual RID canvas_light_shadow_buffer_create(int p_width); + virtual void canvas_light_shadow_buffer_update(RID p_buffer, const Matrix32& p_light_xform, int p_light_mask,float p_near, float p_far, CanvasLightOccluderInstance* p_occluders, CameraMatrix *p_xform_cache); + + //occluder + virtual RID canvas_light_occluder_create(); + virtual void canvas_light_occluder_set_polylines(RID p_occluder, const DVector<Vector2>& p_lines); /* ENVIRONMENT */ @@ -1611,6 +1665,8 @@ public: virtual bool is_environment(const RID& p_rid) const; virtual bool is_shader(const RID& p_rid) const; + virtual bool is_canvas_light_occluder(const RID& p_rid) const; + virtual void free(const RID& p_rid); virtual void init(); diff --git a/drivers/gles2/shaders/SCsub b/drivers/gles2/shaders/SCsub index c665cf9036..9679223b16 100644 --- a/drivers/gles2/shaders/SCsub +++ b/drivers/gles2/shaders/SCsub @@ -3,6 +3,7 @@ Import('env') if env['BUILDERS'].has_key('GLSL120GLES'): env.GLSL120GLES('material.glsl'); env.GLSL120GLES('canvas.glsl'); + env.GLSL120GLES('canvas_shadow.glsl'); env.GLSL120GLES('blur.glsl'); env.GLSL120GLES('copy.glsl'); diff --git a/drivers/gles2/shaders/canvas.glsl b/drivers/gles2/shaders/canvas.glsl index 227a8b7bdd..9022f30d7f 100644 --- a/drivers/gles2/shaders/canvas.glsl +++ b/drivers/gles2/shaders/canvas.glsl @@ -34,6 +34,10 @@ varying vec4 local_rot; uniform vec2 normal_flip; #endif +#ifdef USE_SHADOWS +highp varying vec2 pos; +#endif + #endif #if defined(ENABLE_VAR1_INTERP) @@ -63,6 +67,8 @@ VERTEX_SHADER_CODE outvec = modelview_matrix * outvec; #endif + + #ifdef USE_PIXEL_SNAP outvec.xy=floor(outvec.xy+0.5); @@ -75,6 +81,9 @@ VERTEX_SHADER_CODE light_uv_interp.xy = (light_matrix * outvec).xy; light_uv_interp.zw = outvec.xy-light_pos; +#ifdef USE_SHADOWS + pos=outvec.xy; +#endif #if defined(NORMAL_USED) local_rot.xy=normalize( (modelview_matrix * ( extra_matrix * vec4(1.0,0.0,0.0,0.0) )).xy )*normal_flip.x; @@ -154,6 +163,15 @@ varying vec4 local_rot; uniform sampler2D shadow_texture; uniform float shadow_attenuation; +uniform highp mat4 shadow_matrix; +uniform highp mat4 light_local_matrix; +highp varying vec2 pos; +uniform float shadowpixel_size; + +#ifdef SHADOW_ESM +uniform float shadow_esm_multiplier; +#endif + #endif #endif @@ -173,10 +191,6 @@ void main() { vec3 normal = vec3(0,0,1); #endif -#ifdef USE_MODULATE - - color*=modulate; -#endif color *= texture2D( texture, uv_interp ); #if defined(ENABLE_SCREEN_UV) @@ -191,6 +205,12 @@ FRAGMENT_SHADER_CODE color = vec4(vec3(enc32),1.0); #endif +#ifdef USE_MODULATE + + color*=modulate; +#endif + + #ifdef USE_LIGHTING #if defined(NORMAL_USED) @@ -201,13 +221,96 @@ FRAGMENT_SHADER_CODE vec4 light = texture2D(light_texture,light_uv_interp.xy) * light_color; #ifdef USE_SHADOWS - //this might not be that great on mobile? - float light_dist = length(light_texture.zw); - float light_angle = atan2(light_texture.x,light_texture.z) + 1.0 * 0.5; - float shadow_dist = texture2D(shadow_texture,vec2(light_angle,0)); - if (light_dist>shadow_dist) { - light*=shadow_attenuation; + + + vec2 lpos = (light_local_matrix * vec4(pos,0.0,1.0)).xy; + float angle_to_light = -atan(lpos.x,lpos.y); + float PI = 3.14159265358979323846264; + /*int i = int(mod(floor((angle_to_light+7.0*PI/6.0)/(4.0*PI/6.0))+1.0, 3.0)); // +1 pq os indices estao em ordem 2,0,1 nos arrays + float ang*/ + + float su,sz; + + float abs_angle = abs(angle_to_light); + vec2 point; + float sh; + if (abs_angle<45.0*PI/180.0) { + point = lpos; + sh=0+(1.0/8.0); + } else if (abs_angle>135.0*PI/180.0) { + point = -lpos; + sh = 0.5+(1.0/8.0); + } else if (angle_to_light>0) { + + point = vec2(lpos.y,-lpos.x); + sh = 0.25+(1.0/8.0); + } else { + + point = vec2(-lpos.y,lpos.x); + sh = 0.75+(1.0/8.0); + + } + + + vec4 s = shadow_matrix * vec4(point,0.0,1.0); + s.xyz/=s.w; + su=s.x*0.5+0.5; + sz=s.z*0.5+0.5; + + float shadow_attenuation; + +#ifdef SHADOW_PCF5 + + shadow_attenuation=0.0; + shadow_attenuation += texture2D(shadow_texture,vec2(su,sh)).z<sz?0.0:1.0; + shadow_attenuation += texture2D(shadow_texture,vec2(su+shadowpixel_size,sh)).z<sz?0.0:1.0; + shadow_attenuation += texture2D(shadow_texture,vec2(su+shadowpixel_size*2.0,sh)).z<sz?0.0:1.0; + shadow_attenuation += texture2D(shadow_texture,vec2(su-shadowpixel_size,sh)).z<sz?0.0:1.0; + shadow_attenuation += texture2D(shadow_texture,vec2(su-shadowpixel_size*2.0,sh)).z<sz?0.0:1.0; + shadow_attenuation/=5.0; + +#endif + +#ifdef SHADOW_PCF13 + + shadow_attenuation += texture2D(shadow_texture,vec2(su,sh)).z<sz?0.0:1.0; + shadow_attenuation += texture2D(shadow_texture,vec2(su+shadowpixel_size,sh)).z<sz?0.0:1.0; + shadow_attenuation += texture2D(shadow_texture,vec2(su+shadowpixel_size*2.0,sh)).z<sz?0.0:1.0; + shadow_attenuation += texture2D(shadow_texture,vec2(su+shadowpixel_size*3.0,sh)).z<sz?0.0:1.0; + shadow_attenuation += texture2D(shadow_texture,vec2(su+shadowpixel_size*4.0,sh)).z<sz?0.0:1.0; + shadow_attenuation += texture2D(shadow_texture,vec2(su+shadowpixel_size*5.0,sh)).z<sz?0.0:1.0; + shadow_attenuation += texture2D(shadow_texture,vec2(su+shadowpixel_size*6.0,sh)).z<sz?0.0:1.0; + shadow_attenuation += texture2D(shadow_texture,vec2(su-shadowpixel_size*2.0,sh)).z<sz?0.0:1.0; + shadow_attenuation += texture2D(shadow_texture,vec2(su-shadowpixel_size*3.0,sh)).z<sz?0.0:1.0; + shadow_attenuation += texture2D(shadow_texture,vec2(su-shadowpixel_size*4.0,sh)).z<sz?0.0:1.0; + shadow_attenuation += texture2D(shadow_texture,vec2(su-shadowpixel_size*5.0,sh)).z<sz?0.0:1.0; + shadow_attenuation += texture2D(shadow_texture,vec2(su-shadowpixel_size*6.0,sh)).z<sz?0.0:1.0; + shadow_attenuation/=13.0; + +#endif + +#ifdef SHADOW_ESM + + + { + float unnormalized = su/shadowpixel_size; + float fractional = fract(unnormalized); + unnormalized = floor(unnormalized); + float zc = texture2D(shadow_texture,vec2((unnormalized-0.5)*shadowpixel_size,sh)).z; + float zn = texture2D(shadow_texture,vec2((unnormalized+0.5)*shadowpixel_size,sh)).z; + float z = mix(zc,zn,fractional); + shadow_attenuation=clamp(exp(shadow_esm_multiplier* ( z - sz )),0.0,1.0); } + +#endif + +#if !defined(SHADOW_PCF5) && !defined(SHADOW_PCF13) && !defined(SHADOW_ESM) + + shadow_attenuation = texture2D(shadow_texture,vec2(su+shadowpixel_size,sh)).z<sz?0.0:1.0; + +#endif + + light*=shadow_attenuation; //use shadows #endif diff --git a/drivers/gles2/shaders/canvas_shadow.glsl b/drivers/gles2/shaders/canvas_shadow.glsl new file mode 100644 index 0000000000..40cf321dce --- /dev/null +++ b/drivers/gles2/shaders/canvas_shadow.glsl @@ -0,0 +1,62 @@ +[vertex] + +#ifdef USE_GLES_OVER_GL +#define mediump +#define highp +#else +precision mediump float; +precision mediump int; +#endif + +uniform highp mat4 projection_matrix; +uniform highp mat4 light_matrix; +uniform highp mat4 world_matrix; + +attribute highp vec3 vertex; // attrib:0 + +#ifndef USE_DEPTH_SHADOWS + +varying vec4 position_interp; + +#endif + + +void main() { + + gl_Position = projection_matrix * (light_matrix * (world_matrix * vec4(vertex,1.0))); + +#ifndef USE_DEPTH_SHADOWS + position_interp = gl_Position; +#endif + +} + +[fragment] + +#ifdef USE_GLES_OVER_GL +#define mediump +#define highp +#else +precision mediump float; +precision mediump int; +#endif + +#ifndef USE_DEPTH_SHADOWS + +varying vec4 position_interp; + +#endif + +void main() { + +#ifdef USE_DEPTH_SHADOWS + +#else + highp float depth = ((position_interp.z / position_interp.w) + 1.0) * 0.5 + 0.0;//bias; + highp vec4 comp = fract(depth * vec4(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0)); + comp -= comp.xxyz * vec4(0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0); + gl_FragColor = comp; +#endif + +} + diff --git a/drivers/windows/file_access_windows.cpp b/drivers/windows/file_access_windows.cpp index a6073cbb29..e24685432c 100644 --- a/drivers/windows/file_access_windows.cpp +++ b/drivers/windows/file_access_windows.cpp @@ -28,14 +28,18 @@ /*************************************************************************/ #ifdef WINDOWS_ENABLED +#include <Windows.h> +#include "Shlwapi.h" #include "file_access_windows.h" + #include <sys/types.h> #include <sys/stat.h> #include <wchar.h> #include <tchar.h> #include "print_string.h" + #ifdef _MSC_VER #define S_ISREG(m) ((m)&_S_IFREG) #endif @@ -111,10 +115,20 @@ void FileAccessWindows::close() { //unlink(save_path.utf8().get_data()); //print_line("renaming.."); - _wunlink(save_path.c_str()); //unlink if exists - int rename_error = _wrename((save_path+".tmp").c_str(),save_path.c_str()); + //_wunlink(save_path.c_str()); //unlink if exists + //int rename_error = _wrename((save_path+".tmp").c_str(),save_path.c_str()); + + + bool rename_error; + if (!PathFileExistsW(save_path.c_str())) { + //creating new file + rename_error = _wrename((save_path+".tmp").c_str(),save_path.c_str())!=0; + } else { + //atomic replace for existing file + rename_error = !ReplaceFileW(save_path.c_str(), (save_path+".tmp").c_str(), NULL, 2|4, NULL, NULL); + } save_path=""; - ERR_FAIL_COND( rename_error != 0); + ERR_FAIL_COND( rename_error ); } |