Commit 57dce4b5 authored by Brandon Jones's avatar Brandon Jones Committed by Commit Bot

Support conversion of XR reflection maps to sRGBA8

Ideally this conversion should happen in the XR service and off the JS
thread, but that's an optimization that can happen later. The actual
conversion may need some tweaking in the future, for things like better
tone mapping, but for the time being this gives a usable result.

Bug: 1147569
Change-Id: I0407eee6e4c8337bd376bd43ca0f32987a471055
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2535915
Commit-Queue: Brandon Jones <bajones@chromium.org>
Reviewed-by: default avatarAlexander Cooper <alcooper@chromium.org>
Cr-Commit-Position: refs/heads/master@{#827455}
parent a3dcaffa
...@@ -15,6 +15,37 @@ namespace { ...@@ -15,6 +15,37 @@ namespace {
bool IsPowerOfTwo(uint32_t value) { bool IsPowerOfTwo(uint32_t value) {
return value && (value & (value - 1)) == 0; return value && (value & (value - 1)) == 0;
} }
// This is an inversion of FloatToHalfFloat in ui/gfx/half_float.cc
float HalfFloatToFloat(const uint16_t input) {
uint32_t tmp = (input & 0x7fff) << 13 | (input & 0x8000) << 16;
float tmp2 = *reinterpret_cast<float*>(&tmp);
return tmp2 / 1.9259299444e-34f;
}
// Linear to sRGB converstion as given in
// https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_framebuffer_sRGB.txt
uint8_t LinearToSrgb(float cl) {
float cs = base::ClampToRange(
cl < 0.0031308f ? 12.92f * cl : 1.055f * pow(cl, 0.41666f) - 0.055f, 0.0f,
1.0f);
return static_cast<uint8_t>(255.0f * cs + 0.5f);
}
void Rgba16fToSrgba8(const uint16_t* input,
uint8_t* output,
WTF::wtf_size_t num) {
DCHECK_EQ(num % 4, 0ul);
for (WTF::wtf_size_t i = 0; i < num; i += 4) {
output[i] = LinearToSrgb(HalfFloatToFloat(input[i]));
output[i + 1] = LinearToSrgb(HalfFloatToFloat(input[i + 1]));
output[i + 2] = LinearToSrgb(HalfFloatToFloat(input[i + 2]));
// We won't support non-opaque alpha to make the conversion a bit faster.
output[i + 3] = 255;
}
}
} // namespace } // namespace
namespace blink { namespace blink {
...@@ -48,7 +79,10 @@ XRCubeMap::XRCubeMap(const device::mojom::blink::XRCubeMap& cube_map) { ...@@ -48,7 +79,10 @@ XRCubeMap::XRCubeMap(const device::mojom::blink::XRCubeMap& cube_map) {
WebGLTexture* XRCubeMap::updateWebGLEnvironmentCube( WebGLTexture* XRCubeMap::updateWebGLEnvironmentCube(
WebGLRenderingContextBase* context, WebGLRenderingContextBase* context,
WebGLTexture* texture) const { WebGLTexture* texture,
GLenum internal_format,
GLenum format,
GLenum type) const {
// Ensure a texture was supplied from the passed context and with an // Ensure a texture was supplied from the passed context and with an
// appropriate bound target. // appropriate bound target.
DCHECK(texture); DCHECK(texture);
...@@ -75,17 +109,39 @@ WebGLTexture* XRCubeMap::updateWebGLEnvironmentCube( ...@@ -75,17 +109,39 @@ WebGLTexture* XRCubeMap::updateWebGLEnvironmentCube(
GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
}; };
// Update image for each side of the cube map // Update image for each side of the cube map in the requested format,
for (int i = 0; i < 6; ++i) { // either "srgb8" or "rgba16f".
auto* image = cubemap_images[i]; if (type == GL_UNSIGNED_BYTE) {
auto target = cubemap_targets[i]; // If we've been asked to provide the textures with UNSIGNED_BYTE
// components it means the light probe was created with the "srgb8" format.
gl->TexImage2D(target, 0, GL_RGBA16F, width_and_height_, width_and_height_, // Since ARCore provides texture as half float components, we need to do a
0, GL_RGBA, GL_HALF_FLOAT, image); // conversion first to support this path.
// TODO(https://crbug.com/1148605): Do conversions off the main JS thread.
WTF::wtf_size_t component_count = width_and_height_ * width_and_height_ * 4;
WTF::Vector<uint8_t> sRGB(component_count);
for (int i = 0; i < 6; ++i) {
Rgba16fToSrgba8(cubemap_images[i], sRGB.data(), component_count);
auto target = cubemap_targets[i];
gl->TexImage2D(target, 0, internal_format, width_and_height_,
width_and_height_, 0, format, type, sRGB.data());
}
} else if (type == GL_HALF_FLOAT || type == GL_HALF_FLOAT_OES) {
// If we've been asked to provide the textures with one of the HALF_FLOAT
// types it means the light probe was created with the "rgba16f" format.
// This is ARCore's native format, so no conversion is needed.
for (int i = 0; i < 6; ++i) {
auto* image = cubemap_images[i];
auto target = cubemap_targets[i];
gl->TexImage2D(target, 0, internal_format, width_and_height_,
width_and_height_, 0, format, type, image);
}
} else {
// No other formats are accepted.
NOTREACHED();
} }
// TODO(https://crbug.com/1079007): Restore the texture binding
// gl->BindTexture(GL_TEXTURE_CUBE_MAP, 0);
DrawingBuffer::Client* client = static_cast<DrawingBuffer::Client*>(context); DrawingBuffer::Client* client = static_cast<DrawingBuffer::Client*>(context);
client->DrawingBufferClientRestoreTextureCubeMapBinding(); client->DrawingBufferClientRestoreTextureCubeMapBinding();
......
...@@ -10,6 +10,8 @@ ...@@ -10,6 +10,8 @@
#include "device/vr/public/mojom/vr_service.mojom-blink-forward.h" #include "device/vr/public/mojom/vr_service.mojom-blink-forward.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h" #include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h"
typedef unsigned int GLenum;
namespace blink { namespace blink {
class WebGLRenderingContextBase; class WebGLRenderingContextBase;
...@@ -21,7 +23,10 @@ class XRCubeMap { ...@@ -21,7 +23,10 @@ class XRCubeMap {
explicit XRCubeMap(const device::mojom::blink::XRCubeMap& cube_map); explicit XRCubeMap(const device::mojom::blink::XRCubeMap& cube_map);
WebGLTexture* updateWebGLEnvironmentCube(WebGLRenderingContextBase* context, WebGLTexture* updateWebGLEnvironmentCube(WebGLRenderingContextBase* context,
WebGLTexture* texture) const; WebGLTexture* texture,
GLenum internal_format,
GLenum format,
GLenum type) const;
private: private:
uint32_t width_and_height_ = 0; uint32_t width_and_height_ = 0;
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "third_party/blink/renderer/modules/event_target_modules.h" #include "third_party/blink/renderer/modules/event_target_modules.h"
#include "third_party/blink/renderer/modules/xr/xr_cube_map.h" #include "third_party/blink/renderer/modules/xr/xr_cube_map.h"
#include "third_party/blink/renderer/modules/xr/xr_light_estimate.h" #include "third_party/blink/renderer/modules/xr/xr_light_estimate.h"
#include "third_party/blink/renderer/modules/xr/xr_light_probe_init.h"
#include "third_party/blink/renderer/modules/xr/xr_object_space.h" #include "third_party/blink/renderer/modules/xr/xr_object_space.h"
#include "third_party/blink/renderer/modules/xr/xr_session.h" #include "third_party/blink/renderer/modules/xr/xr_session.h"
...@@ -22,7 +23,14 @@ const double kReflectionChangeDelta = 1000.0; ...@@ -22,7 +23,14 @@ const double kReflectionChangeDelta = 1000.0;
} // namespace } // namespace
XRLightProbe::XRLightProbe(XRSession* session) : session_(session) {} XRLightProbe::XRLightProbe(XRSession* session, XRLightProbeInit* options)
: session_(session) {
if (options->reflectionFormat() == "rgba16f") {
reflection_format_ = kReflectionFormatRGBA16F;
} else {
reflection_format_ = kReflectionFormatSRGBA8;
}
}
XRSpace* XRLightProbe::probeSpace() const { XRSpace* XRLightProbe::probeSpace() const {
if (!probe_space_) { if (!probe_space_) {
......
...@@ -20,6 +20,7 @@ namespace blink { ...@@ -20,6 +20,7 @@ namespace blink {
class TransformationMatrix; class TransformationMatrix;
class XRCubeMap; class XRCubeMap;
class XRLightEstimate; class XRLightEstimate;
class XRLightProbeInit;
class XRSession; class XRSession;
class XRSpace; class XRSpace;
...@@ -27,7 +28,12 @@ class XRLightProbe : public EventTargetWithInlineData { ...@@ -27,7 +28,12 @@ class XRLightProbe : public EventTargetWithInlineData {
DEFINE_WRAPPERTYPEINFO(); DEFINE_WRAPPERTYPEINFO();
public: public:
explicit XRLightProbe(XRSession* session); explicit XRLightProbe(XRSession* session, XRLightProbeInit* options);
enum XRReflectionFormat {
kReflectionFormatSRGBA8 = 0,
kReflectionFormatRGBA16F = 1
};
XRSession* session() const { return session_; } XRSession* session() const { return session_; }
...@@ -44,6 +50,8 @@ class XRLightProbe : public EventTargetWithInlineData { ...@@ -44,6 +50,8 @@ class XRLightProbe : public EventTargetWithInlineData {
XRLightEstimate* getLightEstimate() { return light_estimate_; } XRLightEstimate* getLightEstimate() { return light_estimate_; }
XRCubeMap* getReflectionCubeMap() { return cube_map_.get(); } XRCubeMap* getReflectionCubeMap() { return cube_map_.get(); }
XRReflectionFormat ReflectionFormat() const { return reflection_format_; }
// EventTarget overrides. // EventTarget overrides.
ExecutionContext* GetExecutionContext() const override; ExecutionContext* GetExecutionContext() const override;
const AtomicString& InterfaceName() const override; const AtomicString& InterfaceName() const override;
...@@ -55,6 +63,7 @@ class XRLightProbe : public EventTargetWithInlineData { ...@@ -55,6 +63,7 @@ class XRLightProbe : public EventTargetWithInlineData {
mutable Member<XRSpace> probe_space_; mutable Member<XRSpace> probe_space_;
Member<XRLightEstimate> light_estimate_; Member<XRLightEstimate> light_estimate_;
XRReflectionFormat reflection_format_;
double last_reflection_change_ = 0.0; double last_reflection_change_ = 0.0;
std::unique_ptr<XRCubeMap> cube_map_; std::unique_ptr<XRCubeMap> cube_map_;
}; };
......
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
RuntimeEnabled=WebXRLightEstimation RuntimeEnabled=WebXRLightEstimation
] interface XRLightProbe : EventTarget { ] interface XRLightProbe : EventTarget {
[SameObject] readonly attribute XRSpace probeSpace; [SameObject] readonly attribute XRSpace probeSpace;
attribute EventHandler onreflectionchange; attribute EventHandler onreflectionchange;
}; };
...@@ -1255,14 +1255,27 @@ ScriptPromise XRSession::requestLightProbe(ScriptState* script_state, ...@@ -1255,14 +1255,27 @@ ScriptPromise XRSession::requestLightProbe(ScriptState* script_state,
if (!IsFeatureEnabled(device::mojom::XRSessionFeature::LIGHT_ESTIMATION)) { if (!IsFeatureEnabled(device::mojom::XRSessionFeature::LIGHT_ESTIMATION)) {
exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError, exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
kLightEstimationFeatureNotSupported); kLightEstimationFeatureNotSupported);
return {}; return ScriptPromise();
}
if (light_probe_init->reflectionFormat() != "srgba8" &&
light_probe_init->reflectionFormat() != "rgba16f") {
exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
"Reflection format \"" +
light_probe_init->reflectionFormat() +
"\" not supported.");
return ScriptPromise();
} }
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state); auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise(); ScriptPromise promise = resolver->Promise();
if (!world_light_probe_) { if (!world_light_probe_) {
world_light_probe_ = MakeGarbageCollected<XRLightProbe>(this); // TODO(https://crbug.com/1147569): This is problematic because it means the
// first reflection format that gets requested is the only one that can be
// returned.
world_light_probe_ =
MakeGarbageCollected<XRLightProbe>(this, light_probe_init);
} }
resolver->Resolve(world_light_probe_); resolver->Resolve(world_light_probe_);
......
...@@ -68,23 +68,63 @@ XRWebGLBinding::XRWebGLBinding(XRSession* session, ...@@ -68,23 +68,63 @@ XRWebGLBinding::XRWebGLBinding(XRSession* session,
WebGLTexture* XRWebGLBinding::getReflectionCubeMap( WebGLTexture* XRWebGLBinding::getReflectionCubeMap(
XRLightProbe* light_probe, XRLightProbe* light_probe,
ExceptionState& exception_state) { ExceptionState& exception_state) {
if (!webgl2_ && !webgl_context_->ExtensionsUtil()->IsExtensionEnabled( GLenum internal_format, format, type;
"OES_texture_half_float")) {
if (webgl_context_->isContextLost()) {
exception_state.ThrowDOMException( exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError, DOMExceptionCode::kInvalidStateError,
"WebGL contexts must have the OES_texture_half_float extension enabled " "Cannot get reflection cube map with a lost context.");
"prior to calling getReflectionCubeMap. This restriction does not "
"apply to WebGL 2.0 contexts.");
return nullptr; return nullptr;
} }
// Determine the internal_format, format, and type that will be passed to
// glTexImage2D for each possible light probe reflection format. The formats
// will differ depending on whether we're using WebGL 2 or WebGL 1 with
// extensions.
switch (light_probe->ReflectionFormat()) {
case XRLightProbe::kReflectionFormatRGBA16F:
if (!webgl2_ && !webgl_context_->ExtensionsUtil()->IsExtensionEnabled(
"GL_OES_texture_half_float")) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"WebGL contexts must have the OES_texture_half_float extension "
"enabled "
"prior to calling getReflectionCubeMap with a format of "
"\"rgba16f\". "
"This restriction does not apply to WebGL 2.0 contexts.");
return nullptr;
}
internal_format = webgl2_ ? GL_RGBA16F : GL_RGBA;
format = GL_RGBA;
// Surprisingly GL_HALF_FLOAT and GL_HALF_FLOAT_OES have different values.
type = webgl2_ ? GL_HALF_FLOAT : GL_HALF_FLOAT_OES;
break;
case XRLightProbe::kReflectionFormatSRGBA8:
bool use_srgb =
webgl2_ ||
webgl_context_->ExtensionsUtil()->IsExtensionEnabled("GL_EXT_sRGB");
if (use_srgb) {
internal_format = webgl2_ ? GL_SRGB8_ALPHA8 : GL_SRGB_ALPHA_EXT;
} else {
internal_format = GL_RGBA;
}
format = webgl2_ ? GL_RGBA : internal_format;
type = GL_UNSIGNED_BYTE;
break;
}
XRCubeMap* cube_map = light_probe->getReflectionCubeMap(); XRCubeMap* cube_map = light_probe->getReflectionCubeMap();
if (!cube_map) { if (!cube_map) {
return nullptr; return nullptr;
} }
WebGLTexture* texture = MakeGarbageCollected<WebGLTexture>(webgl_context_); WebGLTexture* texture = MakeGarbageCollected<WebGLTexture>(webgl_context_);
cube_map->updateWebGLEnvironmentCube(webgl_context_, texture); cube_map->updateWebGLEnvironmentCube(webgl_context_, texture, internal_format,
format, type);
return texture; return texture;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment