diff options
author | Jesse Barker <jesse.barker@linaro.org> | 2012-11-15 14:32:12 -0800 |
---|---|---|
committer | Jesse Barker <jesse.barker@linaro.org> | 2012-11-15 14:32:12 -0800 |
commit | ee9d0ecff70999701cc02867c9d93be07a6cfcb7 (patch) | |
tree | b1cd77bef2cf0f733865919e35e52627fd53110e | |
parent | 0cd473c5d57f26fbbe49a2ad8a27d95bd735c723 (diff) | |
parent | 3981d164f058eee239af611c4d96956fc69d32da (diff) | |
download | glmark2-ee9d0ecff70999701cc02867c9d93be07a6cfcb7.tar.gz |
Merge of ~glmark2-dev/glmark2/shadow
Adds SceneShadow to exercise shadow mapping.
-rw-r--r-- | data/shaders/depth.frag | 6 | ||||
-rw-r--r-- | data/shaders/depth.vert | 12 | ||||
-rw-r--r-- | data/shaders/shadow.frag | 17 | ||||
-rw-r--r-- | data/shaders/shadow.vert | 16 | ||||
-rw-r--r-- | src/android.cpp | 1 | ||||
-rw-r--r-- | src/default-benchmarks.h | 1 | ||||
-rw-r--r-- | src/main.cpp | 1 | ||||
-rw-r--r-- | src/mesh.cpp | 8 | ||||
-rw-r--r-- | src/scene-shadow.cpp | 550 | ||||
-rw-r--r-- | src/scene.h | 18 |
10 files changed, 630 insertions, 0 deletions
diff --git a/data/shaders/depth.frag b/data/shaders/depth.frag new file mode 100644 index 0000000..e2a5256 --- /dev/null +++ b/data/shaders/depth.frag @@ -0,0 +1,6 @@ +varying vec3 Normal; + +void main() +{ + gl_FragColor = vec4(Normal, 1.0); +} diff --git a/data/shaders/depth.vert b/data/shaders/depth.vert new file mode 100644 index 0000000..f424032 --- /dev/null +++ b/data/shaders/depth.vert @@ -0,0 +1,12 @@ +attribute vec3 position; +attribute vec3 normal; + +uniform mat4 ModelViewProjectionMatrix; + +varying vec3 Normal; + +void main(void) +{ + Normal = normal; + gl_Position = ModelViewProjectionMatrix * vec4(position, 1.0); +} diff --git a/data/shaders/shadow.frag b/data/shaders/shadow.frag new file mode 100644 index 0000000..6b86ace --- /dev/null +++ b/data/shaders/shadow.frag @@ -0,0 +1,17 @@ +uniform sampler2D ShadowMap; + +varying vec4 Color; +varying vec4 ShadowCoord; + +void main() +{ + vec4 sc_perspective = ShadowCoord / ShadowCoord.w; + sc_perspective.z += 0.1505; + vec4 shadow_value = texture2D(ShadowMap, sc_perspective.st); + float light_distance = shadow_value.z; + float shadow = 1.0; + if (ShadowCoord.w > 0.0 && light_distance < sc_perspective.z) { + shadow = 0.5; + } + gl_FragColor = vec4(shadow * Color.rgb, 1.0); +} diff --git a/data/shaders/shadow.vert b/data/shaders/shadow.vert new file mode 100644 index 0000000..a8ba2b4 --- /dev/null +++ b/data/shaders/shadow.vert @@ -0,0 +1,16 @@ +attribute vec2 position; + +uniform mat4 LightMatrix; +uniform mat4 ModelViewProjectionMatrix; + +varying vec4 ShadowCoord; +varying vec4 Color; + +void main() +{ + Color = MaterialDiffuse; + + vec4 pos4 = vec4(position, 0.0, 1.0); + ShadowCoord = LightMatrix * pos4; + gl_Position = ModelViewProjectionMatrix * pos4; +} diff --git a/src/android.cpp b/src/android.cpp index d9678f4..53a17f7 100644 --- a/src/android.cpp +++ b/src/android.cpp @@ -275,6 +275,7 @@ create_and_add_scenes(std::vector<Scene*>& scenes, Canvas& canvas) scenes.push_back(new SceneIdeas(canvas)); scenes.push_back(new SceneTerrain(canvas)); scenes.push_back(new SceneJellyfish(canvas)); + scenes.push_back(new SceneShadow(canvas)); } diff --git a/src/default-benchmarks.h b/src/default-benchmarks.h index d121e30..45f7672 100644 --- a/src/default-benchmarks.h +++ b/src/default-benchmarks.h @@ -63,6 +63,7 @@ private: benchmarks.push_back("ideas:speed=duration"); benchmarks.push_back("jellyfish"); benchmarks.push_back("terrain"); + benchmarks.push_back("shadow"); benchmarks.push_back("conditionals:vertex-steps=0:fragment-steps=0"); benchmarks.push_back("conditionals:vertex-steps=0:fragment-steps=5"); benchmarks.push_back("conditionals:vertex-steps=5:fragment-steps=0"); diff --git a/src/main.cpp b/src/main.cpp index 36cdf7b..d310a51 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -63,6 +63,7 @@ add_and_register_scenes(vector<Scene*>& scenes, Canvas& canvas) scenes.push_back(new SceneIdeas(canvas)); scenes.push_back(new SceneTerrain(canvas)); scenes.push_back(new SceneJellyfish(canvas)); + scenes.push_back(new SceneShadow(canvas)); for (vector<Scene*>::const_iterator iter = scenes.begin(); iter != scenes.end(); diff --git a/src/mesh.cpp b/src/mesh.cpp index bcc36e6..9ccbcc6 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -543,6 +543,8 @@ void Mesh::render_array() { for (size_t i = 0; i < vertex_format_.size(); i++) { + if (attrib_locations_[i] < 0) + continue; glEnableVertexAttribArray(attrib_locations_[i]); glVertexAttribPointer(attrib_locations_[i], vertex_format_[i].first, GL_FLOAT, GL_FALSE, vertex_stride_, @@ -552,6 +554,8 @@ Mesh::render_array() glDrawArrays(GL_TRIANGLES, 0, vertices_.size()); for (size_t i = 0; i < vertex_format_.size(); i++) { + if (attrib_locations_[i] < 0) + continue; glDisableVertexAttribArray(attrib_locations_[i]); } } @@ -566,6 +570,8 @@ void Mesh::render_vbo() { for (size_t i = 0; i < vertex_format_.size(); i++) { + if (attrib_locations_[i] < 0) + continue; glEnableVertexAttribArray(attrib_locations_[i]); glBindBuffer(GL_ARRAY_BUFFER, vbos_[i]); glVertexAttribPointer(attrib_locations_[i], vertex_format_[i].first, @@ -576,6 +582,8 @@ Mesh::render_vbo() glDrawArrays(GL_TRIANGLES, 0, vertices_.size()); for (size_t i = 0; i < vertex_format_.size(); i++) { + if (attrib_locations_[i] < 0) + continue; glDisableVertexAttribArray(attrib_locations_[i]); } } diff --git a/src/scene-shadow.cpp b/src/scene-shadow.cpp new file mode 100644 index 0000000..4fd27c7 --- /dev/null +++ b/src/scene-shadow.cpp @@ -0,0 +1,550 @@ +// +// Copyright © 2012 Linaro Limited +// +// This file is part of the glmark2 OpenGL (ES) 2.0 benchmark. +// +// glmark2 is free software: you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the Free Software +// Foundation, either version 3 of the License, or (at your option) any later +// version. +// +// glmark2 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 General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// glmark2. If not, see <http://www.gnu.org/licenses/>. +// +// Authors: +// Jesse Barker +// +#include "scene.h" +#include "model.h" +#include "util.h" +#include "log.h" +#include "shader-source.h" +#include "stack.h" + +using std::string; +using std::vector; +using std::map; +using LibMatrix::Stack4; +using LibMatrix::mat4; +using LibMatrix::vec4; +using LibMatrix::vec3; +using LibMatrix::vec2; + +static const vec4 lightPosition(0.0f, 3.0f, 2.0f, 1.0f); + +// +// To create a shadow map, we need a framebuffer object set up for a +// depth-only pass. The render target can then be bound as a texture, +// and the depth values sampled from that texture can be used in the +// distance-from-light computations when rendering the shadow on the +// ground below the rendered object. +// +class DepthRenderTarget +{ + Program program_; + unsigned int canvas_width_; + unsigned int canvas_height_; + unsigned int width_; + unsigned int height_; + unsigned int tex_; + unsigned int fbo_; +public: + DepthRenderTarget() : + canvas_width_(0), + canvas_height_(0), + width_(0), + height_(0), + tex_(0), + fbo_(0) {} + ~DepthRenderTarget() {} + bool setup(unsigned int width, unsigned int height); + void teardown(); + void enable(const mat4& mvp); + void disable(); + unsigned int texture() { return tex_; } + Program& program() { return program_; } +}; + +bool +DepthRenderTarget::setup(unsigned int width, unsigned int height) +{ + canvas_width_ = width; + canvas_height_ = height; + width_ = canvas_width_ * 2; + height_ = canvas_height_ * 2; + + static const string vtx_shader_filename(GLMARK_DATA_PATH"/shaders/depth.vert"); + static const string frg_shader_filename(GLMARK_DATA_PATH"/shaders/depth.frag"); + + ShaderSource vtx_source(vtx_shader_filename); + ShaderSource frg_source(frg_shader_filename); + + if (!Scene::load_shaders_from_strings(program_, vtx_source.str(), frg_source.str())) { + return false; + } + + glGenTextures(1, &tex_); + glBindTexture(GL_TEXTURE_2D, tex_); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width_, height_, 0, + GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, 0); + glBindTexture(GL_TEXTURE_2D, 0); + + glGenFramebuffers(1, &fbo_); + glBindFramebuffer(GL_FRAMEBUFFER, fbo_); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, + tex_, 0); + unsigned int status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + Log::error("DepthRenderState::setup: glCheckFramebufferStatus failed (0x%x)\n", status); + return false; + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + return true; +} + +void +DepthRenderTarget::teardown() +{ + program_.stop(); + program_.release(); + if (tex_) { + glDeleteTextures(1, &tex_); + tex_ = 0; + } + if (fbo_) { + glDeleteFramebuffers(1, &fbo_); + fbo_ = 0; + } +} + +void +DepthRenderTarget::enable(const mat4& mvp) +{ + program_.start(); + program_["ModelViewProjectionMatrix"] = mvp; + glBindFramebuffer(GL_FRAMEBUFFER, fbo_); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, + tex_, 0); + glViewport(0, 0, width_, height_); + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + glClear(GL_DEPTH_BUFFER_BIT); +} + +void DepthRenderTarget::disable() +{ + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glViewport(0, 0, canvas_width_, canvas_height_); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); +} + +// +// The actual shadow pass is really just a quad projected into the scene +// with the horse's shadow cast upon it. In the vertex stage, we compute +// a texture coordinate for the depth texture look-up by transforming the +// current vertex position using a matrix describing the light's view point. +// In the fragment stage, that coordinate is perspective corrected, and +// used to sample the depth texture. If the depth value for that fragment +// (effectively the distance from the light to the object at that point) +// is less than the Z component of that coordinate (effectively the distance +// from the light to the ground at that point) then that location is in shadow. +// +class GroundRenderer +{ + Program program_; + mat4 light_; + Stack4 modelview_; + mat4 projection_; + int positionLocation_; + unsigned int bufferObject_; + unsigned int texture_; + vector<vec2> vertices_; + vector<vec2> texcoords_; + +public: + GroundRenderer() : + positionLocation_(0), + bufferObject_(0) {} + ~GroundRenderer() {} + bool setup(const mat4& projection, unsigned int texture); + void teardown(); + void draw(); +}; + +bool +GroundRenderer::setup(const mat4& projection, unsigned int texture) +{ + projection_ = projection; + texture_ = texture; + + // Program set up + static const vec4 materialDiffuse(0.3f, 0.3f, 0.3f, 1.0f); + static const string vtx_shader_filename(GLMARK_DATA_PATH"/shaders/shadow.vert"); + static const string frg_shader_filename(GLMARK_DATA_PATH"/shaders/shadow.frag"); + ShaderSource vtx_source(vtx_shader_filename); + ShaderSource frg_source(frg_shader_filename); + + vtx_source.add_const("MaterialDiffuse", materialDiffuse); + + if (!Scene::load_shaders_from_strings(program_, vtx_source.str(), frg_source.str())) { + return false; + } + positionLocation_ = program_["position"].location(); + + // Set up the position data for our "quad". + vertices_.push_back(vec2(-1.0, -1.0)); + vertices_.push_back(vec2(1.0, -1.0)); + vertices_.push_back(vec2(-1.0, 1.0)); + vertices_.push_back(vec2(1.0, 1.0)); + + // Set up the VBO and stash our position data in it. + glGenBuffers(1, &bufferObject_); + glBindBuffer(GL_ARRAY_BUFFER, bufferObject_); + glBufferData(GL_ARRAY_BUFFER, vertices_.size() * sizeof(vec2), + &vertices_.front(), GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + // Set up the light matrix with a bias that will convert values + // in the range of [-1, 1] to [0, 1)], then add in the projection + // and the "look at" matrix from the light position. + light_ *= LibMatrix::Mat4::translate(0.5, 0.5, 0.5); + light_ *= LibMatrix::Mat4::scale(0.5, 0.5, 0.5); + light_ *= projection_; + light_ *= LibMatrix::Mat4::lookAt(lightPosition.x(), lightPosition.y(), lightPosition.z(), + 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0); + + return true; +} + +void +GroundRenderer::teardown() +{ + program_.stop(); + program_.release(); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glDeleteBuffers(1, &bufferObject_); + bufferObject_ = 0; + texture_= 0; + vertices_.clear(); +} + +void +GroundRenderer::draw() +{ + // Need to add uniforms for the shadow texture, and transformation to + // "lay the quad down". + modelview_.push(); + modelview_.translate(projection_[0][3], projection_[1][3] - 0.8, projection_[2][3] - 0.5); + modelview_.rotate(-85.0, 1.0, 0.0, 0.0); + modelview_.scale(2.0, 2.0, 2.0); + mat4 mvp(projection_); + mvp *= modelview_.getCurrent(); + + glBindBuffer(GL_ARRAY_BUFFER, bufferObject_); + program_.start(); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture_); + program_["ShadowMap"] = 0; + program_["LightMatrix"] = light_; + program_["ModelViewProjectionMatrix"] = mvp; + + glEnableVertexAttribArray(positionLocation_); + glVertexAttribPointer(positionLocation_, 2, GL_FLOAT, GL_FALSE, 0, 0); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glDisableVertexAttribArray(positionLocation_); + glBindTexture(GL_TEXTURE_2D, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + modelview_.pop(); +} + +class ShadowPrivate +{ + Canvas& canvas_; + DepthRenderTarget depthTarget_; + GroundRenderer ground_; + Program program_; + Stack4 modelview_; + Stack4 projection_; + Mesh mesh_; + vec3 centerVec_; + float radius_; + float rotation_; + float rotationSpeed_; + bool useVbo_; + +public: + ShadowPrivate(Canvas& canvas) : + canvas_(canvas), + radius_(0.0), + rotation_(0.0), + rotationSpeed_(36.0), + useVbo_(true) {} + ~ShadowPrivate() {} + + bool setup(map<string, Scene::Option>& options); + void teardown(); + void update(double elapsedTime); + void draw(); +}; + +bool +ShadowPrivate::setup(map<string, Scene::Option>& options) +{ + // Program object setup + static const string vtx_shader_filename(GLMARK_DATA_PATH"/shaders/light-basic.vert"); + static const string frg_shader_filename(GLMARK_DATA_PATH"/shaders/light-basic.frag"); + static const vec4 materialDiffuse(1.0f, 1.0f, 1.0f, 1.0f); + + ShaderSource vtx_source(vtx_shader_filename); + ShaderSource frg_source(frg_shader_filename); + + vtx_source.add_const("LightSourcePosition", lightPosition); + vtx_source.add_const("MaterialDiffuse", materialDiffuse); + + if (!Scene::load_shaders_from_strings(program_, vtx_source.str(), frg_source.str())) { + return false; + } + + // Model setup + Model::find_models(); + Model model; + bool modelLoaded = model.load("horse"); + + if(!modelLoaded) { + return false; + } + + model.calculate_normals(); + + // Mesh setup + vector<std::pair<Model::AttribType, int> > attribs; + attribs.push_back(std::pair<Model::AttribType, int>(Model::AttribTypePosition, 3)); + attribs.push_back(std::pair<Model::AttribType, int>(Model::AttribTypeNormal, 3)); + model.convert_to_mesh(mesh_, attribs); + + useVbo_ = (options["use-vbo"].value == "true"); + bool interleave = (options["interleave"].value == "true"); + mesh_.vbo_update_method(Mesh::VBOUpdateMethodMap); + mesh_.interleave(interleave); + + if (useVbo_) { + mesh_.build_vbo(); + } + else { + mesh_.build_array(); + } + + // Calculate a projection matrix that is a good fit for the model + vec3 maxVec = model.maxVec(); + vec3 minVec = model.minVec(); + vec3 diffVec = maxVec - minVec; + centerVec_ = maxVec + minVec; + centerVec_ /= 2.0; + float diameter = diffVec.length(); + radius_ = diameter / 2; + float fovy = 2.0 * atanf(radius_ / (2.0 + radius_)); + fovy /= M_PI; + fovy *= 180.0; + float aspect(static_cast<float>(canvas_.width())/static_cast<float>(canvas_.height())); + projection_.perspective(fovy, aspect, 2.0, 50.0); + + if (!depthTarget_.setup(canvas_.width(), canvas_.height())) { + Log::error("Failed to set up the render target for the depth pass\n"); + return false; + } + + if (!ground_.setup(projection_.getCurrent(), depthTarget_.texture())) { + Log::error("Failed to set up the ground renderer\n"); + return false; + } + + return true; +} + + +void +ShadowPrivate::teardown() +{ + depthTarget_.teardown(); + ground_.teardown(); + program_.stop(); + program_.release(); + mesh_.reset(); +} + +void +ShadowPrivate::update(double elapsedTime) +{ + rotation_ = rotationSpeed_ * elapsedTime; +} + +void +ShadowPrivate::draw() +{ + // To perform the depth pass, set up the model-view transformation so + // that we're looking at the horse from the light position. That will + // give us the appropriate view for the shadow. + modelview_.push(); + modelview_.loadIdentity(); + modelview_.lookAt(lightPosition.x(), lightPosition.y(), lightPosition.z(), + 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0); + modelview_.rotate(rotation_, 0.0f, 1.0f, 0.0f); + mat4 mvp(projection_.getCurrent()); + mvp *= modelview_.getCurrent(); + modelview_.pop(); + + // Enable the depth render target with our transformation and render. + depthTarget_.enable(mvp); + vector<GLint> attrib_locations; + attrib_locations.push_back(depthTarget_.program()["position"].location()); + attrib_locations.push_back(depthTarget_.program()["normal"].location()); + mesh_.set_attrib_locations(attrib_locations); + if (useVbo_) { + mesh_.render_vbo(); + } + else { + mesh_.render_array(); + } + depthTarget_.disable(); + + // Ground rendering using the above generated texture... + ground_.draw(); + + // Draw the "normal" view of the horse + modelview_.push(); + modelview_.translate(-centerVec_.x(), -centerVec_.y(), -(centerVec_.z() + 2.0 + radius_)); + modelview_.rotate(rotation_, 0.0f, 1.0f, 0.0f); + mvp = projection_.getCurrent(); + mvp *= modelview_.getCurrent(); + + program_.start(); + program_["ModelViewProjectionMatrix"] = mvp; + + // Load the NormalMatrix uniform in the shader. The NormalMatrix is the + // inverse transpose of the model view matrix. + LibMatrix::mat4 normal_matrix(modelview_.getCurrent()); + normal_matrix.inverse().transpose(); + program_["NormalMatrix"] = normal_matrix; + attrib_locations.clear(); + attrib_locations.push_back(program_["position"].location()); + attrib_locations.push_back(program_["normal"].location()); + mesh_.set_attrib_locations(attrib_locations); + if (useVbo_) { + mesh_.render_vbo(); + } + else { + mesh_.render_array(); + } + + // Per-frame cleanup + modelview_.pop(); +} + +SceneShadow::SceneShadow(Canvas& canvas) : + Scene(canvas, "shadow"), + priv_(0) +{ + options_["use-vbo"] = Scene::Option("use-vbo", "true", + "Whether to use VBOs for rendering", + "false,true"); + options_["interleave"] = Scene::Option("interleave", "false", + "Whether to interleave vertex attribute data", + "false,true"); +} + +SceneShadow::~SceneShadow() +{ + delete priv_; +} + +bool +SceneShadow::supported(bool show_errors) +{ + static const string oes_depth_texture("GL_OES_depth_texture"); + static const string arb_depth_texture("GL_ARB_depth_texture"); + if (!GLExtensions::support(oes_depth_texture) && + !GLExtensions::support(arb_depth_texture)) { + if (show_errors) { + Log::error("We do not have the depth texture extension!!!\n"); + } + + return false; + } + return true; +} + +bool +SceneShadow::load() +{ + running_ = false; + return true; +} + +void +SceneShadow::unload() +{ +} + +bool +SceneShadow::setup() +{ + // If the scene isn't supported, don't bother to go through setup. + if (!supported(false) || !Scene::setup()) + { + return false; + } + + priv_ = new ShadowPrivate(canvas_); + if (!priv_->setup(options_)) { + delete priv_; + priv_ = 0; + return false; + } + + // Set core scene timing after actual initialization so we don't measure + // set up time. + startTime_ = Util::get_timestamp_us() / 1000000.0; + lastUpdateTime_ = startTime_; + running_ = true; + + return true; +} + +void +SceneShadow::teardown() +{ + // Add scene-specific teardown here + priv_->teardown(); + Scene::teardown(); +} + +void +SceneShadow::update() +{ + Scene::update(); + // Add scene-specific update here + priv_->update(lastUpdateTime_ - startTime_); +} + +void +SceneShadow::draw() +{ + priv_->draw(); +} + +Scene::ValidationResult +SceneShadow::validate() +{ + return Scene::ValidationUnknown; +} diff --git a/src/scene.h b/src/scene.h index a455fbd..dd77ba9 100644 --- a/src/scene.h +++ b/src/scene.h @@ -556,4 +556,22 @@ public: void draw(); ValidationResult validate(); }; + +class ShadowPrivate; +class SceneShadow : public Scene +{ + ShadowPrivate* priv_; +public: + SceneShadow(Canvas& canvas); + ~SceneShadow(); + bool supported(bool show_errors); + bool load(); + void unload(); + bool setup(); + void teardown(); + void update(); + void draw(); + ValidationResult validate(); +}; + #endif |