/* QGL Renderer Ivan Pelipenko peri4ko@yandex.ru This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ #define GL_GLEXT_PROTOTYPES #include "renderer.h" #include "glmesh.h" #include "glshaders.h" #include "gltexture_manager.h" #include "qglview.h" #include #include using namespace QGLEngineShaders; Renderer::Renderer(QGLView * view_) : RendererBase(view_) , fbo_ds(view_, QVector() << GL_RGBA16F << GL_RGBA32F << GL_RGBA16F << GL_RGBA16F << GL_RGBA16F << GL_RGBA32F << GL_RGBA16F) , fbo_out(view_, obrBuffersCount, false, GL_RGBA16F) , rend_mat(this) , rend_service(this) , rend_selection(this) , tone_proc(this) , tex_env(view_, 512) { quad = Primitive::plane(2., 2.); cam_light = new Light(); cam_light->intensity = 0.75; cam_light->setName("Camera_Light"); shader_files[srSelectionFill] = "selection.glsl"; shader_files[srSelectionHalo] = "selection_halo.glsl"; shader_files[srSelectionApply] = "selection_apply.glsl"; shader_files[srSelectionFrame] = "selection_frame.glsl"; shader_files[srServiceFill] = "service_fill.glsl"; shader_files[srServiceFrame] = "service_frame.glsl"; shader_files[srServiceLine] = "service_line.glsl"; shader_files[srGeometrySolidPass] = "ds_geom.glsl"; shader_files[srGeometryTransparentPass] = "ds_geom.glsl"; shader_files[srLightOmniPass] = "ds_light.glsl"; shader_files[srLightSpotPass] = "ds_light.glsl"; shader_files[srFinalPass] = "ds_final.glsl"; shader_files[srTonemapPass] = "ds_tonemap.glsl"; shader_defines[srGeometrySolidPass] << "SOLID"; shader_defines[srLightSpotPass] << "SPOT"; edit_mode = need_init_shaders = true; camera_light_mode = QGLView::clmAuto; } Renderer::~Renderer() { delete quad; delete cam_light; qDeleteAll(shaders.values()); if (shader_fxaa) delete shader_fxaa; } void Renderer::init(int width, int height) { fbo_ds.reinit(); fbo_out.reinit(); quad->reinit(); buffer_materials.reinit(); buffer_lights.reinit(); buffer_lights_pos.reinit(); textures_maps.reinit(); textures_empty.reinit(); resize(width, height); rend_mat.init(width, height); rend_service.init(width, height); rend_selection.init(width, height); tone_proc.init(); initQuad(quad); initTextureArrays(); initCoeffTextures(); markReloadTextures(); tex_env.init(); if (is_grabbing) fbo_out.enablePixelBuffer(); need_init_shaders = true; } void Renderer::resize(int width, int height) { rend_mat.resize(width, height); rend_service.resize(width, height); rend_selection.resize(width, height); fbo_ds.resize(width, height); fbo_out.resize(width, height); tone_proc.resize(); } void Renderer::reloadShaders() { QMapIterator it(shader_files); qDeleteAll(shaders.values()); shaders.clear(); if (shader_fxaa) delete shader_fxaa; shader_fxaa = nullptr; if (tone_proc.shader_sum) delete tone_proc.shader_sum; tone_proc.shader_sum = nullptr; QString dir = ":/shaders/"; while (it.hasNext()) { it.next(); loadShadersMulti(shaders[it.key()], dir + it.value(), true, shader_defines.value(it.key())); } loadShadersMulti(tone_proc.shader_sum, dir + "sum.glsl", false); QStringList fxaa_defs; fxaa_defs << "FXAA_PC 1" << "FXAA_GLSL_130 1" << "FXAA_QUALITY__PRESET 15"; loadShaders(shader_fxaa, QStringList() << (dir + "fxaa.vert") << (dir + "fxaa.frag"), true, fxaa_defs); for (auto * e: fb_effects) { e->reloadShaders(); e->is_loaded = true; } need_init_shaders = true; view->scene()->setLightsChanged(); view->scene()->setTreeStructChanged(); view->scene()->setMaterialsChanged(); } bool Renderer::bindShader(Renderer::ShaderRole role, QOpenGLShaderProgram ** ret) { QOpenGLShaderProgram * prog = shaders.value(role); if (ret) *ret = prog; if (!prog) return false; if (!prog->isLinked()) return false; prog->bind(); return true; } bool Renderer::bindShader(QOpenGLShaderProgram * sp) { if (!sp) return true; if (!sp->isLinked()) return true; if (!sp->bind()) return false; return true; } void Renderer::initShaders() { if (!need_init_shaders) return; need_init_shaders = false; initUniformBuffer(shaders.value(srGeometrySolidPass), &buffer_materials, bpMaterials, "QGLMaterialData"); initUniformBuffer(shaders.value(srGeometryTransparentPass), &buffer_materials, bpMaterials, "QGLMaterialData"); QOpenGLShaderProgram * prog = 0; for (ShaderRole role: {srLightOmniPass, srLightSpotPass}) { initUniformBuffer(shaders.value(role), &buffer_materials, bpMaterials, "QGLMaterialData"); initUniformBuffer(shaders.value(role), &buffer_lights, bpLightParameters, "QGLLightParameterData"); initUniformBuffer(shaders.value(role), &buffer_lights_pos, bpLightPositions, "QGLLightPositionData"); if (!bindShader(role, &prog)) continue; for (int i = 0; i < 5; ++i) prog->setUniformValue(QString("tex_%1").arg(i).toLatin1().constData(), i); prog->setUniformValue("tex_coeffs[0]", (int)Renderer::dbrBuffersCount); prog->setUniformValue("tex_env", (int)Renderer::dbrBuffersCount + 1); } if (bindShader(srFinalPass, &prog)) { prog->setUniformValue("tex_g1", 0); prog->setUniformValue("tex_s_0", 1); prog->setUniformValue("tex_s_1", 2); prog->setUniformValue("tex_t_0", 3); prog->setUniformValue("tex_t_1", 4); } if (bindShader(srGeometrySolidPass, &prog)) { setUniformMaps(prog); } if (bindShader(srGeometryTransparentPass, &prog)) { setUniformMaps(prog); } if (bindShader(srTonemapPass, &prog)) { prog->setUniformValue("tex_0", 0); prog->setUniformValue("tex_sum", 1); } } void Renderer::releaseShader() { view->glUseProgram(0); } QVector Renderer::getFreePlanes(int count) { prev_write_plane = cur_write_plane; QVector ret; bool output_done = false; const int total_count = 4; for (int i = 0; i < total_count; ++i) { int plane = obrGeneral0 + i; if (prev_write_plane == plane) continue; if (!output_done) { fbo_out.setWriteBuffer(plane); cur_write_plane = plane; output_done = true; continue; } ret << plane; if (ret.size() == count) break; } return ret; } void Renderer::fillObjectsBuffer(const ObjectBaseList & ol, RenderPass pass) { cur_objects_.resize(ol.size()); for (int i = 0; i < ol.size(); ++i) { Object & so(cur_objects_[i]); ObjectBase * o = ol[i]; if (o->material()) { so.material = o->material()->_index; so.color = QVector4D(1, 1, 1, 1); } else { so.material = 0; so.color = QColor2QVector(o->color()); } so.object_id = o->id(); o->worldTransform().transposed().copyDataTo(so.modelmatrix); o->textureGLMatrix().copyDataTo(so.texturematrix); // qDebug() << "load obj" << o->name() << o->textureMatrix() << tmat; } // qDebug() << "fillObjectsBuffer" << ol.size(); } void Renderer::renderObjects(Scene & scene, RenderPass pass) { QOpenGLExtraFunctions * f = view; QMapIterator it(scene.geometries_used[pass]); bool emit_pos_change = false; while (it.hasNext()) { it.next(); Mesh * mesh = it.key(); if (mesh->isObjectsChanged(pass)) { mesh->setObjectsChanged(pass, false); emit_pos_change = true; fillObjectsBuffer(it.value(), pass); // qDebug() << "loadObjects" << pass << cur_objects_.size(); mesh->loadObjects(f, cur_objects_, pass); } if (mesh->isSelectionChanged(pass) && edit_mode) { mesh->setSelectionChanged(pass, false); fillSelectionsBuffer(rend_selection.cur_selections_, it.value()); // qDebug() << "fillSelectionsBuffer" << pass << rend_selection.cur_selections_.size(); mesh->loadSelections(f, rend_selection.cur_selections_, pass); } // qDebug() << "draw" << pass << it.value().size(); mesh->draw(f, it.value().size(), pass); } if (emit_pos_change) emit view->objectsPositionChanged(); } void Renderer::renderLight(int first_wr_buff, bool clear_only) { QOpenGLShaderProgram * prog = 0; Camera * cam = view->camera(); for (int i = 0; i < 2; ++i) { view->glActiveTexture(GL_TEXTURE0 + Renderer::dbrBuffersCount + i); view->glBindTexture(GL_TEXTURE_2D, tex_coeff[i]); } fbo_ds.bindColorTextures(); fbo_out.bind(); tex_env.bind((int)Renderer::dbrBuffersCount + 1); typedef QPair PassPair; QVector passes; passes << PassPair(srLightOmniPass, Light::Omni) << PassPair(srLightSpotPass, Light::Cone); QColor back = view->fogColor(); back.setAlpha(0); foreach(PassPair pass, passes) { if (bindShader(pass.first, &prog)) { fbo_out.setWriteBuffer(first_wr_buff + pass.second); glClearFramebuffer(back, false); if (clear_only) continue; setUniformCamera(prog, cam); setUniformViewCorners(prog, cam); prog->setUniformValue("lights_start", lights_start[pass.second]); prog->setUniformValue("lights_count", (int)cur_lights[pass.second].size()); prog->setUniformValue("fog_color", view->fogColor()); prog->setUniformValue("fog_decay", qMax(view->fogDecay(), 0.001f)); prog->setUniformValue("fog_density", view->fogDensity()); prog->setUniformValue("view_mat", cam->viewMatrix().inverted().toGenericMatrix<3, 3>()); renderQuad(prog, quad, cam); } } } void Renderer::renderScene() { timings.clear(); Measurer phase(&timings); phase.begin("init"); initShaders(); tex_env.load(); QOpenGLExtraFunctions * f = view; Scene & scene(*(view->scene())); Camera * cam = view->camera(); QOpenGLShaderProgram * prog = 0; bool scene_changed = scene.prepare(); scene.destroyUnused(f); phase.end(); /// reload materials on change phase.begin("scene reload"); if (scene_changed || scene.need_reload_materials) { rend_selection.generateObjectsID(scene); reloadMaterials(scene); if (edit_mode) recreateMaterialThumbnails(); emit view->materialsChanged(); } phase.end(); /// material thumbnails phase.begin("materials"); if (edit_mode && !scene_changed) { rend_mat.procQueue(); } phase.end(); /// lights phase.begin("lights prepare"); cur_lights = scene.lights_used; bool use_camlight = (camera_light_mode == QGLView::clmOn); if ((camera_light_mode == QGLView::clmAuto) && cur_lights.isEmpty()) { use_camlight = true; } if (use_camlight) { cur_lights[Light::Omni] << cam_light; cam_light->setPos(cam->pos()); } if (scene.lights_changed) { scene.lights_changed = false; reloadLightsParameters(cur_lights); } reloadLightsPositions(cam); phase.end(); /// selection phase.begin("selection"); if (edit_mode) { rend_selection.renderSelection(scene); } phase.end(); /// solid geometry pass phase.begin("geometry solid"); fbo_ds.bind(); glEnableDepth(); glClearFramebuffer(); if (bindShader(srGeometrySolidPass, &prog)) { setUniformCamera(prog, cam); textures_empty.bind(f, tarEmpty); textures_maps.bind(f, tarMaps); renderObjects(scene, rpSolid); } fbo_ds.blit(dbrNormalZ, fbo_ds.id(), dbrNormalZSolid, fbo_ds.rect(), fbo_ds.rect()); fbo_ds.blit(dbrSpecularReflect, fbo_ds.id(), dbrSpecularReflectSolid, fbo_ds.rect(), fbo_ds.rect()); fbo_ds.release(); phase.end(); /// lighting passes phase.begin("... light"); renderLight(obrSolidOmni, scene.geometries_used[rpSolid].isEmpty()); phase.end(); /// transparent geometry pass phase.begin("geometry trans"); fbo_ds.bind(); glEnableDepth(); fbo_ds.setWriteBuffers({0, 1, 2, 3, 4}); glClearFramebuffer(Qt::black, false); fbo_ds.setWriteBuffers(); if (bindShader(srGeometryTransparentPass, &prog)) { setUniformCamera(prog, cam); textures_empty.bind(f, tarEmpty); textures_maps.bind(f, tarMaps); renderObjects(scene, rpTransparent); } fbo_ds.release(); phase.end(); /// lighting passes phase.begin("... light"); renderLight(obrTransparentOmni, scene.geometries_used[rpTransparent].isEmpty()); phase.end(); /// blending layers phase.begin("blending"); if (bindShader(srFinalPass, &prog)) { fbo_out.bindColorTexture(obrSolidOmni, 1); fbo_out.bindColorTexture(obrSolidSpot, 2); fbo_out.bindColorTexture(obrTransparentOmni, 3); fbo_out.bindColorTexture(obrTransparentSpot, 4); fbo_out.setWriteBuffer(obrLighting); renderQuad(prog, quad); } phase.end(); fbo_out.bind(); cur_write_plane = obrLighting; /// tonemapping phase.begin("tonemap"); tone_proc.process(); auto free = getFreePlanes(0); if (bindShader(srTonemapPass, &prog)) { prog->setUniformValue("gamma", gamma_); prog->setUniformValue("frame_max", tone_proc.frameMax()); // qDebug() << tone_proc.frameMax(); fbo_out.bindColorTexture(prev_write_plane, 0); renderQuad(prog, quad); } else { fbo_out.blit(prev_write_plane, fbo_out.id(), cur_write_plane, fbo_out.rect(), fbo_out.rect()); } phase.end(); /// FXAA phase.begin("fxaa"); if (view->isFeatureEnabled(QGLView::qglFXAA)) { prog = shader_fxaa; if (bindShader(prog)) { auto free = getFreePlanes(0); setUniformCamera(prog, 0, true, fbo_out.size()); fbo_out.bindColorTexture(prev_write_plane); renderQuad(prog, quad, 0, false); } } phase.end(); /// custom effects for (auto * e: fb_effects) { if (!e->isEnabled()) continue; phase.begin("fb effect " + e->name()); e->reloadShadersInternal(); e->drawInternal(); phase.end(); } fbo_out.release(); /// apply hovers and selection frame phase.begin("service"); if (edit_mode) { rend_selection.drawSelection(fbo_out, cur_write_plane); rend_service.renderService(); } else { fbo_out.blit(cur_write_plane, 0, 0, fbo_out.rect(), QRect(QPoint(), view->size() * view->devicePixelRatio())); } phase.end(); /// grab framebuffer phase.begin("grab"); if (is_grabbing) { fbo_out.queryImage(0); last_img = fbo_out.getImage().mirrored(); // qDebug() << last_img.size(); } phase.end(); } void Renderer::setCameraLightMode(int m) { camera_light_mode = m; view->scene()->setLightsChanged(); } void Renderer::setGrabImage(bool on) { is_grabbing = on; // fbo_out.enablePixelBuffer(); } void Renderer::addFramebufferEffect(FramebufferEffectBase * e) { e->r = this; fb_effects << e; }