Skip to content

Commit

Permalink
player: port the depth-aware mouse interaction to WebGL.
Browse files Browse the repository at this point in the history
Again just a copy from the Mouse Interaction example.
  • Loading branch information
mosra committed Sep 19, 2018
1 parent 92291d7 commit b175dba
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 9 deletions.
9 changes: 8 additions & 1 deletion src/player/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,14 @@ if(CORRADE_TARGET_EMSCRIPTEN)
StbTrueTypeFont)
endif()

add_executable(magnum-player Player.cpp)
set(Player_SRCS Player.cpp)

if(MAGNUM_TARGET_WEBGL)
corrade_add_resource(Player_RESOURCES resources.conf)
list(APPEND Player_SRCS ${Player_RESOURCES})
endif()

add_executable(magnum-player ${Player_SRCS})
target_link_libraries(magnum-player PRIVATE
Magnum::Application
Magnum::GL
Expand Down
44 changes: 44 additions & 0 deletions src/player/DepthReinterpretShader.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018
Vladimír Vondruš <[email protected]>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/

uniform highp sampler2D depthTexture;

in mediump vec2 textureCoordinates;

out highp vec3 reinterpretDepth;

void main() {
/* Convert from range 0.0 - 1.0 to 0 - 0xffffff (we have a 24bit depth and
floats have 24bit mantissa so this should preserve everything), then
separate that into three 8bit values and then unpack each 8bit value
back to 0.0 - 1.0 again in order to make the WebGL RGBA8 pipeline happy.
All the fancy packing algos from https://stackoverflow.com/q/9882716 and
elsewhere were not treating depth = 1.0 correctly, so I'm doing my own
thing here. */
highp float depth = texture(depthTexture, textureCoordinates).r;
highp uint depthI = uint(depth*float(0xffffffu));
highp uvec3 depthIV = uvec3((depthI >> 16), (depthI >> 8), depthI) & 0xffu;
reinterpretDepth = vec3(depthIV)/255.0;
}
54 changes: 54 additions & 0 deletions src/player/DepthReinterpretShader.vert
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018
Vladimír Vondruš <[email protected]>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/

out mediump vec2 textureCoordinates;

void main() {
/* -1 0 1 3
1 0-------+-------2
| | /
0 | | /
| | /
-1 +-------+
| /
| /
| /
-3 1 */
gl_Position = vec4(gl_VertexID == 2 ? 3.0 : -1.0,
gl_VertexID == 1 ? -3.0 : 1.0, 0.0, 1.0);

/* 0 0.5 1 2
1 0-------+-------2
| | /
0.5 | | /
| | /
0 +-------+
| /
| /
| /
-1 1 */
textureCoordinates = vec2(gl_VertexID == 2 ? 2.0 : 0.0,
gl_VertexID == 1 ? -1.0 : 1.0);
}
157 changes: 149 additions & 8 deletions src/player/Player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,15 @@
#include <emscripten/emscripten.h>
#endif

#ifdef MAGNUM_TARGET_WEBGL
#include <Corrade/Utility/Resource.h>
#include <Magnum/GL/Framebuffer.h>
#include <Magnum/GL/Shader.h>
#include <Magnum/GL/Version.h>
#include <Magnum/GL/Renderbuffer.h>
#include <Magnum/GL/RenderbufferFormat.h>
#endif

namespace Magnum {

namespace {
Expand All @@ -78,6 +87,35 @@ using namespace Math::Literals;
typedef SceneGraph::Object<SceneGraph::TranslationRotationScalingTransformation3D> Object3D;
typedef SceneGraph::Scene<SceneGraph::TranslationRotationScalingTransformation3D> Scene3D;

#ifdef MAGNUM_TARGET_WEBGL
class DepthReinterpretShader: public GL::AbstractShaderProgram {
public:
explicit DepthReinterpretShader(NoCreateT): GL::AbstractShaderProgram{NoCreate} {}
explicit DepthReinterpretShader();

DepthReinterpretShader& bindDepthTexture(GL::Texture2D& texture) {
texture.bind(7);
return *this;
}
};

DepthReinterpretShader::DepthReinterpretShader() {
GL::Shader vert{GL::Version::GLES300, GL::Shader::Type::Vertex};
GL::Shader frag{GL::Version::GLES300, GL::Shader::Type::Fragment};

Utility::Resource rs{"data"};
vert.addSource(rs.get("DepthReinterpretShader.vert"));
frag.addSource(rs.get("DepthReinterpretShader.frag"));

CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag}));

attachShaders({vert, frag});
CORRADE_INTERNAL_ASSERT_OUTPUT(link());

setUniform(uniformLocation("depthTexture"), 7);
}
#endif

constexpr const Float WidgetHeight{36.0f};
constexpr const Float LabelHeight{36.0f};
constexpr const Vector2 ButtonSize{96.0f, WidgetHeight};
Expand Down Expand Up @@ -217,6 +255,14 @@ class Player: public Platform::Application, public Interconnect::Receiver {
Float _lastDepth;
Vector2i _lastPosition{-1};
Vector3 _rotationPoint, _translationPoint;
#ifdef MAGNUM_TARGET_WEBGL
GL::Framebuffer _depthFramebuffer{NoCreate};
GL::Texture2D _depth{NoCreate};
GL::Framebuffer _reinterpretFramebuffer{NoCreate};
GL::Renderbuffer _reinterpretDepth{NoCreate};
GL::Mesh _fullscreenTriangle{NoCreate};
DepthReinterpretShader _reinterpretShader{NoCreate};
#endif
};

class ColoredDrawable: public SceneGraph::Drawable3D {
Expand Down Expand Up @@ -248,10 +294,20 @@ class TexturedDrawable: public SceneGraph::Drawable3D {
Player* app;
#endif

Player::Player(const Arguments& arguments):
Platform::Application{arguments, Configuration{}
Player::Player(const Arguments& arguments): Platform::Application{arguments,
Configuration{}
.setTitle("Magnum Player")
.setWindowFlags(Configuration::WindowFlag::Resizable)}
.setWindowFlags(Configuration::WindowFlag::Resizable),
GLConfiguration{}
#ifdef MAGNUM_TARGET_WEBGL
/* Needed to ensure the canvas depth buffer is always Depth24Stencil8,
stencil size is 0 by default, some browser enable stencil for that
(Chrome) and some don't (Firefox) and thus our texture format for
blitting might not always match. */
.setDepthBufferSize(24)
.setStencilBufferSize(8)
#endif
}
{
Utility::Arguments args;
#ifndef CORRADE_TARGET_EMSCRIPTEN
Expand Down Expand Up @@ -290,6 +346,36 @@ Player::Player(const Arguments& arguments):
Interconnect::connect(_baseUiPlane->fullsize, &Ui::Button::tapped, *this, &Player::toggleFullsize);
#endif

/* Setup the depth aware mouse interaction -- on WebGL we can't just read
depth. The only possibility to read depth is to use a depth texture and
read it from a shader, then reinterpret as color and write to a RGBA
texture which can finally be read back using glReadPixels(). However,
with a depth texture we can't use multisampling so I'm instead blitting
the depth from the default framebuffer to another framebuffer with an
attached depth texture and then processing that texture with a custom
shader to reinterpret the depth as RGBA values, packing 8 bit of the
depth into each channel. That's finally read back on the client. */
#ifdef MAGNUM_TARGET_WEBGL
_depth = GL::Texture2D{};
_depth.setMinificationFilter(GL::SamplerFilter::Nearest)
.setMagnificationFilter(GL::SamplerFilter::Nearest)
.setWrapping(GL::SamplerWrapping::ClampToEdge)
/* The format is set to combined depth/stencil in hope it will match
the browser depth/stencil format, requested in the GLConfiguration
above. If it won't, the blit() won't work properly. */
.setStorage(1, GL::TextureFormat::Depth24Stencil8, framebufferSize());
_depthFramebuffer = GL::Framebuffer{{{}, framebufferSize()}};
_depthFramebuffer.attachTexture(GL::Framebuffer::BufferAttachment::Depth, _depth, 0);

_reinterpretDepth = GL::Renderbuffer{};
_reinterpretDepth.setStorage(GL::RenderbufferFormat::RGBA8, framebufferSize());
_reinterpretFramebuffer = GL::Framebuffer{{{}, framebufferSize()}};
_reinterpretFramebuffer.attachRenderbuffer(GL::Framebuffer::ColorAttachment{0}, _reinterpretDepth);
_reinterpretShader = DepthReinterpretShader{};
_fullscreenTriangle = GL::Mesh{};
_fullscreenTriangle.setCount(3);
#endif

/* Setup plugin defaults */
{
PluginManager::PluginMetadata* const metadata = _manager.metadata("TinyGltfImporter");
Expand Down Expand Up @@ -743,6 +829,9 @@ void TexturedDrawable::draw(const Matrix4& transformationMatrix, SceneGraph::Cam
}

void Player::drawEvent() {
#ifdef MAGNUM_TARGET_WEBGL /* Another FB could be bound from the depth read */
GL::defaultFramebuffer.bind();
#endif
GL::defaultFramebuffer.clear(GL::FramebufferClear::Color|GL::FramebufferClear::Depth);

if(_data) {
Expand Down Expand Up @@ -789,6 +878,12 @@ void Player::drawEvent() {
CPU */
if(_data && _data->player.state() == Animation::State::Playing) redraw();

#ifdef MAGNUM_TARGET_WEBGL
/* The rendered depth buffer might get lost later, so resolve it to our
depth texture before swapping it to the canvas */
GL::Framebuffer::blit(GL::defaultFramebuffer, _depthFramebuffer, GL::defaultFramebuffer.viewport(), GL::FramebufferBlit::Depth);
#endif

swapBuffers();
}

Expand Down Expand Up @@ -823,17 +918,63 @@ void Player::viewportEvent(ViewportEvent& event) {
_baseUiPlane->modelInfo.setText(_data->modelInfo);
updateAnimationTime(_data->elapsedTimeAnimationDestination);
}

/* Recreate depth reading textures and renderbuffers that depend on
viewport size */
#ifdef MAGNUM_TARGET_WEBGL
_depth = GL::Texture2D{};
_depth.setMinificationFilter(GL::SamplerFilter::Nearest)
.setMagnificationFilter(GL::SamplerFilter::Nearest)
.setWrapping(GL::SamplerWrapping::ClampToEdge)
.setStorage(1, GL::TextureFormat::Depth24Stencil8, event.framebufferSize());
_depthFramebuffer.attachTexture(GL::Framebuffer::BufferAttachment::Depth, _depth, 0);

_reinterpretDepth = GL::Renderbuffer{};
_reinterpretDepth.setStorage(GL::RenderbufferFormat::RGBA8, event.framebufferSize());
_reinterpretFramebuffer.attachRenderbuffer(GL::Framebuffer::ColorAttachment{0}, _reinterpretDepth);

_reinterpretFramebuffer.setViewport({{}, event.framebufferSize()});
#endif
}

Float Player::depthAt(const Vector2i& position) {
const Vector2i fbPosition{position.x(), GL::defaultFramebuffer.viewport().sizeY() - position.y() - 1};
const Range2Di area = Range2Di::fromSize(fbPosition, Vector2i{1}).padded(Vector2i{2});

/* Easy on sane platforms */
#ifndef MAGNUM_TARGET_WEBGL
GL::defaultFramebuffer.mapForRead(GL::DefaultFramebuffer::ReadAttachment::Front);
Image2D data = GL::defaultFramebuffer.read(
Range2Di::fromSize(fbPosition, Vector2i{1}).padded(Vector2i{2}),
{GL::PixelFormat::DepthComponent, GL::PixelType::Float});

return Math::min(Containers::arrayCast<const Float>(data.data()));
Image2D image = GL::defaultFramebuffer.read(area, {GL::PixelFormat::DepthComponent, GL::PixelType::Float});

return Math::min(Containers::arrayCast<const Float>(image.data()));

/* On WebGL we first need to resolve the multisampled backbuffer depth to a
texture -- that needs to be done right in the draw event otherwise the
data might get lost -- then read that via a custom shader and manually
pack the 24 depth bits to a RGBA8 output. It's not possible to just
glReadPixels() the depth, we need to read a color, moreover Firefox
doesn't allow us to read anything else than RGBA8 so we can't just use
floatBitsToUint() and read R32UI back, we have to pack the values. */
#else
_reinterpretFramebuffer.clearColor(0, Vector4{})
.bind();
_reinterpretShader.bindDepthTexture(_depth);
GL::Renderer::enable(GL::Renderer::Feature::ScissorTest);
GL::Renderer::setScissor(area);
_fullscreenTriangle.draw(_reinterpretShader);
GL::Renderer::disable(GL::Renderer::Feature::ScissorTest);

Image2D image = _reinterpretFramebuffer.read(area, {PixelFormat::RGBA8Unorm});

/* Unpack the values back. Can't just use UnsignedInt as the values are
packed as big-endian. */
Float depth[25];
auto packed = Containers::arrayCast<const Math::Vector4<UnsignedByte>>(image.data());
for(std::size_t i = 0; i != packed.size(); ++i)
depth[i] = Math::unpack<Float, UnsignedInt, 24>((packed[i].x() << 16) | (packed[i].y() << 8) | packed[i].z());

return Math::min(depth);
#endif
}

Vector3 Player::unproject(const Vector2i& position, Float depth) const {
Expand Down
7 changes: 7 additions & 0 deletions src/player/resources.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
group=data

[file]
filename=DepthReinterpretShader.frag

[file]
filename=DepthReinterpretShader.vert

0 comments on commit b175dba

Please sign in to comment.