| // Copyright 2012 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "gpu/command_buffer/service/gles2_cmd_decoder.h" | |
| #include <limits.h> | |
| #include <stddef.h> | |
| #include <stdint.h> | |
| #include <stdio.h> | |
| #include <algorithm> | |
| #include <array> | |
| #include <bit> | |
| #include <cmath> | |
| #include <limits> | |
| #include <list> | |
| #include <map> | |
| #include <memory> | |
| #include <optional> | |
| #include <set> | |
| #include <string_view> | |
| #include <unordered_map> | |
| #include <utility> | |
| #include "base/compiler_specific.h" | |
| #include "base/containers/flat_set.h" | |
| #include "base/containers/heap_array.h" | |
| #include "base/containers/queue.h" | |
| #include "base/containers/span.h" | |
| #include "base/debug/alias.h" | |
| #include "base/debug/dump_without_crashing.h" | |
| #include "base/feature_list.h" | |
| #include "base/functional/bind.h" | |
| #include "base/functional/callback.h" | |
| #include "base/functional/callback_helpers.h" | |
| #include "base/hash/legacy_hash.h" | |
| #include "base/logging.h" | |
| #include "base/memory/raw_ptr.h" | |
| #include "base/notimplemented.h" | |
| #include "base/numerics/safe_conversions.h" | |
| #include "base/numerics/safe_math.h" | |
| #include "base/strings/string_number_conversions.h" | |
| #include "base/strings/string_view_util.h" | |
| #include "base/strings/stringprintf.h" | |
| #include "base/trace_event/trace_event.h" | |
| #include "build/build_config.h" | |
| #include "gpu/command_buffer/common/debug_marker_manager.h" | |
| #include "gpu/command_buffer/common/gles2_cmd_copy_texture_chromium_utils.h" | |
| #include "gpu/command_buffer/common/gles2_cmd_format.h" | |
| #include "gpu/command_buffer/common/gles2_cmd_utils.h" | |
| #include "gpu/command_buffer/common/mailbox.h" | |
| #include "gpu/command_buffer/common/sync_token.h" | |
| #include "gpu/command_buffer/service/buffer_manager.h" | |
| #include "gpu/command_buffer/service/command_buffer_service.h" | |
| #include "gpu/command_buffer/service/context_group.h" | |
| #include "gpu/command_buffer/service/context_state.h" | |
| #include "gpu/command_buffer/service/decoder_client.h" | |
| #include "gpu/command_buffer/service/error_state.h" | |
| #include "gpu/command_buffer/service/feature_info.h" | |
| #include "gpu/command_buffer/service/framebuffer_manager.h" | |
| #include "gpu/command_buffer/service/gl_utils.h" | |
| #include "gpu/command_buffer/service/gles2_cmd_clear_framebuffer.h" | |
| #include "gpu/command_buffer/service/gles2_cmd_copy_tex_image.h" | |
| #include "gpu/command_buffer/service/gles2_cmd_copy_texture_chromium.h" | |
| #include "gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h" | |
| #include "gpu/command_buffer/service/gles2_cmd_validation.h" | |
| #include "gpu/command_buffer/service/gles2_query_manager.h" | |
| #include "gpu/command_buffer/service/gpu_fence_manager.h" | |
| #include "gpu/command_buffer/service/gpu_state_tracer.h" | |
| #include "gpu/command_buffer/service/gpu_tracer.h" | |
| #include "gpu/command_buffer/service/logger.h" | |
| #include "gpu/command_buffer/service/memory_tracking.h" | |
| #include "gpu/command_buffer/service/multi_draw_manager.h" | |
| #include "gpu/command_buffer/service/program_manager.h" | |
| #include "gpu/command_buffer/service/renderbuffer_manager.h" | |
| #include "gpu/command_buffer/service/sampler_manager.h" | |
| #include "gpu/command_buffer/service/shader_manager.h" | |
| #include "gpu/command_buffer/service/shader_translator.h" | |
| #include "gpu/command_buffer/service/shared_image/shared_image_factory.h" | |
| #include "gpu/command_buffer/service/shared_image/shared_image_representation.h" | |
| #include "gpu/command_buffer/service/texture_manager.h" | |
| #include "gpu/command_buffer/service/transform_feedback_manager.h" | |
| #include "gpu/command_buffer/service/vertex_array_manager.h" | |
| #include "gpu/command_buffer/service/vertex_attrib_manager.h" | |
| #include "gpu/config/gpu_finch_features.h" | |
| #include "gpu/config/gpu_preferences.h" | |
| #include "third_party/perfetto/include/perfetto/tracing/track.h" | |
| #include "ui/gfx/buffer_types.h" | |
| #include "ui/gfx/color_space.h" | |
| #include "ui/gfx/geometry/point.h" | |
| #include "ui/gfx/geometry/rect.h" | |
| #include "ui/gfx/geometry/rect_conversions.h" | |
| #include "ui/gfx/geometry/rect_f.h" | |
| #include "ui/gfx/geometry/size.h" | |
| #include "ui/gfx/geometry/transform.h" | |
| #include "ui/gfx/gpu_fence.h" | |
| #include "ui/gfx/gpu_memory_buffer_handle.h" | |
| #include "ui/gfx/overlay_plane_data.h" | |
| #include "ui/gfx/overlay_priority_hint.h" | |
| #include "ui/gfx/video_types.h" | |
| #include "ui/gl/gl_bindings.h" | |
| #include "ui/gl/gl_context.h" | |
| #include "ui/gl/gl_enums.h" | |
| #include "ui/gl/gl_fence.h" | |
| #include "ui/gl/gl_gl_api_implementation.h" | |
| #include "ui/gl/gl_implementation.h" | |
| #include "ui/gl/gl_surface.h" | |
| #include "ui/gl/gl_version_info.h" | |
| #include "ui/gl/gpu_preference.h" | |
| #include "ui/gl/gpu_timing.h" | |
| #include "ui/gl/init/create_gr_gl_interface.h" | |
| #include "ui/gl/scoped_make_current.h" | |
| // Note: this undefs far and near so include this after other Windows headers. | |
| #include "third_party/angle/src/image_util/loadimage.h" | |
| namespace gpu { | |
| namespace gles2 { | |
| namespace { | |
| const char kOESDerivativeExtension[] = "GL_OES_standard_derivatives"; | |
| const char kOESFboRenderMipmapExtension[] = "GL_OES_fbo_render_mipmap"; | |
| const char kEXTFragDepthExtension[] = "GL_EXT_frag_depth"; | |
| const char kEXTDrawBuffersExtension[] = "GL_EXT_draw_buffers"; | |
| const char kEXTShaderTextureLodExtension[] = "GL_EXT_shader_texture_lod"; | |
| const char kWEBGLMultiDrawExtension[] = "GL_WEBGL_multi_draw"; | |
| const char kWEBGLDrawInstancedBaseVertexBaseInstanceExtension[] = | |
| "GL_WEBGL_draw_instanced_base_vertex_base_instance"; | |
| const char kWEBGLMultiDrawInstancedBaseVertexBaseInstanceExtension[] = | |
| "GL_WEBGL_multi_draw_instanced_base_vertex_base_instance"; | |
| template <typename MANAGER_TYPE, typename OBJECT_TYPE> | |
| GLuint GetClientId(const MANAGER_TYPE* manager, const OBJECT_TYPE* object) { | |
| DCHECK(manager); | |
| GLuint client_id = 0; | |
| if (object) { | |
| manager->GetClientId(object->service_id(), &client_id); | |
| } | |
| return client_id; | |
| } | |
| template <typename OBJECT_TYPE> | |
| GLuint GetServiceId(const OBJECT_TYPE* object) { | |
| return object ? object->service_id() : 0; | |
| } | |
| struct Vec4f { | |
| explicit Vec4f(const Vec4& data) { | |
| data.GetValues(v); | |
| } | |
| GLfloat v[4]; | |
| }; | |
| struct TexSubCoord3D { | |
| TexSubCoord3D(int _xoffset, int _yoffset, int _zoffset, | |
| int _width, int _height, int _depth) | |
| : xoffset(_xoffset), | |
| yoffset(_yoffset), | |
| zoffset(_zoffset), | |
| width(_width), | |
| height(_height), | |
| depth(_depth) {} | |
| int xoffset; | |
| int yoffset; | |
| int zoffset; | |
| int width; | |
| int height; | |
| int depth; | |
| }; | |
| void GL_APIENTRY GLDebugMessageCallback(GLenum source, | |
| GLenum type, | |
| GLuint id, | |
| GLenum severity, | |
| GLsizei length, | |
| const GLchar* message, | |
| const GLvoid* user_param) { | |
| Logger* error_logger = static_cast<Logger*>(const_cast<void*>(user_param)); | |
| LogGLDebugMessage(source, type, id, severity, length, message, error_logger); | |
| } | |
| } // namespace | |
| class GLES2DecoderImpl; | |
| // Local versions of the SET_GL_ERROR macros | |
| #define LOCAL_SET_GL_ERROR(error, function_name, msg) \ | |
| ERRORSTATE_SET_GL_ERROR(error_state_.get(), error, function_name, msg) | |
| #define LOCAL_SET_GL_ERROR_INVALID_ENUM(function_name, value, label) \ | |
| ERRORSTATE_SET_GL_ERROR_INVALID_ENUM(error_state_.get(), function_name, \ | |
| static_cast<uint32_t>(value), label) | |
| #define LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER(function_name) \ | |
| ERRORSTATE_COPY_REAL_GL_ERRORS_TO_WRAPPER(error_state_.get(), function_name) | |
| #define LOCAL_PEEK_GL_ERROR(function_name) \ | |
| ERRORSTATE_PEEK_GL_ERROR(error_state_.get(), function_name) | |
| #define LOCAL_CLEAR_REAL_GL_ERRORS(function_name) \ | |
| ERRORSTATE_CLEAR_REAL_GL_ERRORS(error_state_.get(), function_name) | |
| #define LOCAL_PERFORMANCE_WARNING(msg) \ | |
| PerformanceWarning(__FILE__, __LINE__, msg) | |
| #define LOCAL_RENDER_WARNING(msg) \ | |
| RenderWarning(__FILE__, __LINE__, msg) | |
| // Check that certain assumptions the code makes are true. There are places in | |
| // the code where shared memory is passed direclty to GL. Example, glUniformiv, | |
| // glShaderSource. The command buffer code assumes GLint and GLsizei (and maybe | |
| // a few others) are 32bits. If they are not 32bits the code will have to change | |
| // to call those GL functions with service side memory and then copy the results | |
| // to shared memory, converting the sizes. | |
| static_assert(sizeof(GLint) == sizeof(uint32_t), // NOLINT | |
| "GLint should be the same size as uint32_t"); | |
| static_assert(sizeof(GLsizei) == sizeof(uint32_t), // NOLINT | |
| "GLsizei should be the same size as uint32_t"); | |
| static_assert(sizeof(GLfloat) == sizeof(float), // NOLINT | |
| "GLfloat should be the same size as float"); | |
| // TODO(kbr): the use of this anonymous namespace core dumps the | |
| // linker on Mac OS X 10.6 when the symbol ordering file is used | |
| // namespace { | |
| // Return true if a character belongs to the ASCII subset as defined in | |
| // GLSL ES 1.0 spec section 3.1. | |
| static bool CharacterIsValidForGLES(unsigned char c) { | |
| // Printing characters are valid except " $ ` @ \ ' DEL. | |
| if (c >= 32 && c <= 126 && | |
| c != '"' && | |
| c != '$' && | |
| c != '`' && | |
| c != '@' && | |
| c != '\\' && | |
| c != '\'') { | |
| return true; | |
| } | |
| // Horizontal tab, line feed, vertical tab, form feed, carriage return | |
| // are also valid. | |
| if (c >= 9 && c <= 13) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| static bool StringIsValidForGLES(const std::string& str) { | |
| return str.length() == 0 || std::ranges::all_of(str, CharacterIsValidForGLES); | |
| } | |
| DisallowedFeatures::DisallowedFeatures() = default; | |
| DisallowedFeatures::~DisallowedFeatures() = default; | |
| DisallowedFeatures::DisallowedFeatures(const DisallowedFeatures&) = default; | |
| // This class prevents any GL errors that occur when it is in scope from | |
| // being reported to the client. | |
| class ScopedGLErrorSuppressor { | |
| public: | |
| explicit ScopedGLErrorSuppressor( | |
| const char* function_name, ErrorState* error_state); | |
| ScopedGLErrorSuppressor(const ScopedGLErrorSuppressor&) = delete; | |
| ScopedGLErrorSuppressor& operator=(const ScopedGLErrorSuppressor&) = delete; | |
| ~ScopedGLErrorSuppressor(); | |
| private: | |
| const char* function_name_; | |
| raw_ptr<ErrorState> error_state_; | |
| }; | |
| // Temporarily changes a decoder's bound texture and restore it when this | |
| // object goes out of scope. Also temporarily switches to using active texture | |
| // unit zero in case the client has changed that to something invalid. | |
| class ScopedTextureBinder { | |
| public: | |
| explicit ScopedTextureBinder(ContextState* state, | |
| ErrorState* error_state, | |
| GLuint id, | |
| GLenum target); | |
| ScopedTextureBinder(const ScopedTextureBinder&) = delete; | |
| ScopedTextureBinder& operator=(const ScopedTextureBinder&) = delete; | |
| ~ScopedTextureBinder(); | |
| private: | |
| raw_ptr<ContextState> state_; | |
| raw_ptr<ErrorState> error_state_; | |
| GLenum target_; | |
| }; | |
| // Temporarily changes a decoder's bound frame buffer and restore it when this | |
| // object goes out of scope. | |
| class ScopedFramebufferBinder { | |
| public: | |
| explicit ScopedFramebufferBinder(GLES2DecoderImpl* decoder, GLuint id); | |
| ScopedFramebufferBinder(const ScopedFramebufferBinder&) = delete; | |
| ScopedFramebufferBinder& operator=(const ScopedFramebufferBinder&) = delete; | |
| ~ScopedFramebufferBinder(); | |
| private: | |
| raw_ptr<GLES2DecoderImpl> decoder_; | |
| }; | |
| // Temporarily create and bind a single sample copy of the currently bound | |
| // framebuffer using CopyTexImage2D. This is useful as a workaround for drivers | |
| // that have broken implementations of ReadPixels on multisampled framebuffers. | |
| // See http://crbug.com/890002 | |
| class ScopedFramebufferCopyBinder { | |
| public: | |
| explicit ScopedFramebufferCopyBinder(GLES2DecoderImpl* decoder, | |
| GLint x = 0, | |
| GLint y = 0, | |
| GLint width = 0, | |
| GLint height = 0); | |
| ScopedFramebufferCopyBinder(const ScopedFramebufferCopyBinder&) = delete; | |
| ScopedFramebufferCopyBinder& operator=(const ScopedFramebufferCopyBinder&) = | |
| delete; | |
| ~ScopedFramebufferCopyBinder(); | |
| private: | |
| raw_ptr<GLES2DecoderImpl> decoder_; | |
| std::unique_ptr<ScopedFramebufferBinder> framebuffer_binder_; | |
| GLuint temp_texture_; | |
| GLuint temp_framebuffer_; | |
| }; | |
| // Temporarily changes a decoder's PIXEL_UNPACK_BUFFER to 0 and set pixel unpack | |
| // params to default, and restore them when this object goes out of scope. | |
| class ScopedPixelUnpackState { | |
| public: | |
| explicit ScopedPixelUnpackState(ContextState* state); | |
| ScopedPixelUnpackState(const ScopedPixelUnpackState&) = delete; | |
| ScopedPixelUnpackState& operator=(const ScopedPixelUnpackState&) = delete; | |
| ~ScopedPixelUnpackState(); | |
| private: | |
| raw_ptr<ContextState> state_; | |
| }; | |
| // Temporarily resets UNPACK_ROW_LENGTH and UNPACK_IMAGE_HEIGHT in the real | |
| // driver to their initial values (0) around a compressed texture upload, | |
| // restoring the tracked values when it goes out of scope. Unlike | |
| // ScopedPixelUnpackState it does not unbind the pixel unpack buffer: | |
| // compressed uploads legitimately source from it. See the comment in | |
| // DoCompressedTexImage for why compressed dispatches need this. | |
| class ScopedCompressedUnpackStateScrub { | |
| public: | |
| explicit ScopedCompressedUnpackStateScrub(ContextState* state); | |
| ScopedCompressedUnpackStateScrub(const ScopedCompressedUnpackStateScrub&) = | |
| delete; | |
| ScopedCompressedUnpackStateScrub& operator=( | |
| const ScopedCompressedUnpackStateScrub&) = delete; | |
| ~ScopedCompressedUnpackStateScrub(); | |
| private: | |
| raw_ptr<ContextState> state_; | |
| bool active_ = false; | |
| }; | |
| // Encapsulates an OpenGL texture. | |
| class BackTexture { | |
| public: | |
| explicit BackTexture(GLES2DecoderImpl* decoder); | |
| BackTexture(const BackTexture&) = delete; | |
| BackTexture& operator=(const BackTexture&) = delete; | |
| ~BackTexture(); | |
| // Create a new render texture. | |
| void Create(); | |
| // Set the initial size and format of a render texture or resize it. | |
| bool AllocateStorage(const gfx::Size& size, GLenum format, bool zero); | |
| // Copy the contents of the currently bound frame buffer. | |
| void Copy(); | |
| // Destroy the render texture. This must be explicitly called before | |
| // destroying this object. | |
| void Destroy(); | |
| // Invalidate the texture. This can be used when a context is lost and it is | |
| // not possible to make it current in order to free the resource. | |
| void Invalidate(); | |
| // The bind point for the texture. | |
| GLenum Target(); | |
| scoped_refptr<TextureRef> texture_ref() { return texture_ref_; } | |
| GLuint id() const { | |
| return texture_ref_ ? texture_ref_->service_id() : 0; | |
| } | |
| gfx::Size size() const { | |
| return size_; | |
| } | |
| gl::GLApi* api() const; | |
| private: | |
| MemoryTypeTracker memory_tracker_; | |
| size_t bytes_allocated_; | |
| gfx::Size size_; | |
| raw_ptr<GLES2DecoderImpl> decoder_; | |
| scoped_refptr<TextureRef> texture_ref_; | |
| }; | |
| // Encapsulates an OpenGL frame buffer. | |
| class BackFramebuffer { | |
| public: | |
| explicit BackFramebuffer(GLES2DecoderImpl* decoder); | |
| BackFramebuffer(const BackFramebuffer&) = delete; | |
| BackFramebuffer& operator=(const BackFramebuffer&) = delete; | |
| ~BackFramebuffer(); | |
| // Create a new frame buffer. | |
| void Create(); | |
| // Attach a color render buffer to a frame buffer. | |
| void AttachRenderTexture(BackTexture* texture); | |
| // Destroy the frame buffer. This must be explicitly called before destroying | |
| // this object. | |
| void Destroy(); | |
| // Invalidate the frame buffer. This can be used when a context is lost and it | |
| // is not possible to make it current in order to free the resource. | |
| void Invalidate(); | |
| // See glCheckFramebufferStatusEXT. | |
| GLenum CheckStatus(); | |
| GLuint id() const { | |
| return id_; | |
| } | |
| gl::GLApi* api() const; | |
| private: | |
| raw_ptr<GLES2DecoderImpl> decoder_; | |
| GLuint id_; | |
| }; | |
| struct FenceCallback { | |
| FenceCallback() : fence(gl::GLFence::Create()) { DCHECK(fence); } | |
| FenceCallback(FenceCallback&&) = default; | |
| FenceCallback& operator=(FenceCallback&&) = default; | |
| std::vector<base::OnceClosure> callbacks; | |
| std::unique_ptr<gl::GLFence> fence; | |
| }; | |
| // For ensuring that client-side glRenderbufferStorageMultisampleEXT | |
| // calls go down the correct code path. | |
| enum ForcedMultisampleMode { | |
| kForceExtMultisampledRenderToTexture, | |
| kDoNotForce | |
| }; | |
| // } // anonymous namespace. | |
| // static | |
| const unsigned int GLES2Decoder::kDefaultStencilMask = | |
| static_cast<unsigned int>(-1); | |
| bool GLES2Decoder::GetServiceTextureId(uint32_t client_texture_id, | |
| uint32_t* service_texture_id) { | |
| return false; | |
| } | |
| uint32_t GLES2Decoder::GetAndClearBackbufferClearBitsForTest() { | |
| return 0; | |
| } | |
| GLES2Decoder::GLES2Decoder(DecoderClient* client, | |
| CommandBufferServiceBase* command_buffer_service, | |
| Outputter* outputter) | |
| : CommonDecoder(client, command_buffer_service), outputter_(outputter) { | |
| DCHECK(outputter_); | |
| } | |
| GLES2Decoder::~GLES2Decoder() = default; | |
| bool GLES2Decoder::initialized() const { | |
| return initialized_; | |
| } | |
| TextureBase* GLES2Decoder::GetTextureBase(uint32_t client_id) { | |
| return nullptr; | |
| } | |
| void GLES2Decoder::SetLevelInfo(uint32_t client_id, | |
| int level, | |
| unsigned internal_format, | |
| unsigned width, | |
| unsigned height, | |
| unsigned depth, | |
| unsigned format, | |
| unsigned type, | |
| const gfx::Rect& cleared_rect) {} | |
| void GLES2Decoder::BeginDecoding() {} | |
| void GLES2Decoder::EndDecoding() {} | |
| std::string_view GLES2Decoder::GetLogPrefix() { | |
| return GetLogger()->GetLogPrefix(); | |
| } | |
| void GLES2Decoder::SetLogCommands(bool log_commands) { | |
| log_commands_ = log_commands; | |
| } | |
| Outputter* GLES2Decoder::outputter() const { | |
| return outputter_; | |
| } | |
| int GLES2Decoder::GetRasterDecoderId() const { | |
| NOTREACHED(); | |
| } | |
| // This class implements GLES2Decoder so we don't have to expose all the GLES2 | |
| // cmd stuff to outside this class. | |
| class GLES2DecoderImpl : public GLES2Decoder, public ErrorStateClient { | |
| public: | |
| GLES2DecoderImpl(DecoderClient* client, | |
| CommandBufferServiceBase* command_buffer_service, | |
| Outputter* outputter, | |
| ContextGroup* group); | |
| GLES2DecoderImpl(const GLES2DecoderImpl&) = delete; | |
| GLES2DecoderImpl& operator=(const GLES2DecoderImpl&) = delete; | |
| ~GLES2DecoderImpl() override; | |
| error::Error DoCommands(unsigned int num_commands, | |
| const volatile void* buffer, | |
| int num_entries, | |
| int* entries_processed) override; | |
| template <bool DebugImpl> | |
| error::Error DoCommandsImpl(unsigned int num_commands, | |
| const volatile void* buffer, | |
| int num_entries, | |
| int* entries_processed); | |
| // Overridden from GLES2Decoder. | |
| base::WeakPtr<DecoderContext> AsWeakPtr() override; | |
| gpu::ContextResult Initialize(const scoped_refptr<gl::GLSurface>& surface, | |
| const scoped_refptr<gl::GLContext>& context, | |
| bool offscreen, | |
| ContextType context_type, | |
| bool lose_context_when_out_of_memory) override; | |
| void Destroy(bool have_context) override; | |
| void SetSurface(const scoped_refptr<gl::GLSurface>& surface) override; | |
| void ReleaseSurface() override; | |
| bool ResizeOffscreenFramebuffer(const gfx::Size& size); | |
| bool MakeCurrent() override; | |
| gl::GLApi* api() const { return state_.api(); } | |
| GLES2Util* GetGLES2Util() override { return &util_; } | |
| gl::GLContext* GetGLContext() override { return context_.get(); } | |
| gl::GLSurface* GetGLSurface() override { return surface_.get(); } | |
| ContextGroup* GetContextGroup() override { return group_.get(); } | |
| const FeatureInfo* GetFeatureInfo() const override { | |
| return feature_info_.get(); | |
| } | |
| Capabilities GetCapabilities() override; | |
| GLCapabilities GetGLCapabilities() override; | |
| void RestoreState(const ContextState* prev_state) override; | |
| void RestoreActiveTexture() const override { state_.RestoreActiveTexture(); } | |
| void RestoreAllTextureUnitAndSamplerBindings( | |
| const ContextState* prev_state) const override { | |
| state_.RestoreAllTextureUnitAndSamplerBindings(prev_state); | |
| } | |
| void RestoreActiveTextureUnitBinding(unsigned int target) const override { | |
| state_.RestoreActiveTextureUnitBinding(target); | |
| } | |
| void RestoreBufferBindings() const override { | |
| state_.RestoreBufferBindings(); | |
| } | |
| void RestoreGlobalState() const override { | |
| state_.RestoreGlobalState(nullptr); | |
| } | |
| void RestoreProgramBindings() const override { | |
| state_.RestoreProgramSettings(nullptr, false); | |
| } | |
| void RestoreTextureUnitBindings(unsigned unit) const override { | |
| state_.RestoreTextureUnitBindings(unit, nullptr); | |
| } | |
| void RestoreVertexAttribArray(unsigned index) override { | |
| RestoreStateForAttrib(index, true); | |
| } | |
| void PauseTransformFeedback() override { | |
| if (state_.bound_transform_feedback.get() && | |
| state_.bound_transform_feedback->active() && | |
| !state_.bound_transform_feedback->paused()) { | |
| state_.api()->glPauseTransformFeedbackFn(); | |
| } | |
| } | |
| void RestoreBufferBinding(unsigned int target) override; | |
| void RestoreFramebufferBindings() const override; | |
| void RestoreRenderbufferBindings() override; | |
| void RestoreTextureState(unsigned service_id) override; | |
| void ClearDeviceWindowRectangles() const; | |
| void RestoreDeviceWindowRectangles() const override; | |
| void ClearAllAttributes() const override; | |
| void RestoreAllAttributes() const override; | |
| QueryManager* GetQueryManager() override { return query_manager_.get(); } | |
| void SetQueryCallback(unsigned int query_client_id, | |
| base::OnceClosure callback) override; | |
| void CancelAllQueries() override; | |
| GpuFenceManager* GetGpuFenceManager() override { | |
| return gpu_fence_manager_.get(); | |
| } | |
| FramebufferManager* GetFramebufferManager() override { | |
| return framebuffer_manager_.get(); | |
| } | |
| TransformFeedbackManager* GetTransformFeedbackManager() override { | |
| return transform_feedback_manager_.get(); | |
| } | |
| VertexArrayManager* GetVertexArrayManager() override { | |
| return vertex_array_manager_.get(); | |
| } | |
| bool HasPendingQueries() const override; | |
| void ProcessPendingQueries(bool did_finish) override; | |
| bool HasMoreIdleWork() const override; | |
| void PerformIdleWork() override; | |
| bool HasPollingWork() const override; | |
| void PerformPollingWork() override; | |
| void WaitForReadPixels(base::OnceClosure callback) override; | |
| Logger* GetLogger() override; | |
| void BeginDecoding() override; | |
| void EndDecoding() override; | |
| ErrorState* GetErrorState() override; | |
| const ContextState* GetContextState() override { return &state_; } | |
| scoped_refptr<ShaderTranslatorInterface> GetTranslator(GLenum type) override; | |
| scoped_refptr<ShaderTranslatorInterface> GetOrCreateTranslator(GLenum type); | |
| void SetIgnoreCachedStateForTest(bool ignore) override; | |
| void SetForceShaderNameHashingForTest(bool force) override; | |
| uint32_t GetAndClearBackbufferClearBitsForTest() override; | |
| void ProcessFinishedAsyncTransfers(); | |
| bool GetServiceTextureId(uint32_t client_texture_id, | |
| uint32_t* service_texture_id) override; | |
| TextureBase* GetTextureBase(uint32_t client_id) override; | |
| void SetLevelInfo(uint32_t client_id, | |
| int level, | |
| unsigned internal_format, | |
| unsigned width, | |
| unsigned height, | |
| unsigned depth, | |
| unsigned format, | |
| unsigned type, | |
| const gfx::Rect& cleared_rect) override; | |
| // Bind the framebuffer `fbo` and perform any workarounds needed. | |
| void BindFramebuffer(unsigned target, uint32_t service_id) const override; | |
| // Restores the current state to the user's settings. | |
| void RestoreCurrentFramebufferBindings(); | |
| // Sets DEPTH_TEST, STENCIL_TEST and color mask for the current framebuffer. | |
| void ApplyDirtyState(); | |
| // These check the state of the currently bound framebuffer or the | |
| // backbuffer if no framebuffer is bound. | |
| // Check with all attached and enabled color attachments. | |
| bool BoundFramebufferAllowsChangesToAlphaChannel(); | |
| bool BoundFramebufferHasDepthAttachment(); | |
| bool BoundFramebufferHasStencilAttachment(); | |
| // Overriden from ErrorStateClient. | |
| void OnContextLostError() override; | |
| void OnOutOfMemoryError() override; | |
| // Ensure Renderbuffer corresponding to last DoBindRenderbuffer() is bound. | |
| void EnsureRenderbufferBound(); | |
| // Helpers to facilitate calling into compatible extensions. | |
| void RenderbufferStorageMultisampleWithWorkaround(GLenum target, | |
| GLsizei samples, | |
| GLenum internal_format, | |
| GLsizei width, | |
| GLsizei height, | |
| ForcedMultisampleMode mode); | |
| void RenderbufferStorageMultisampleHelper(GLenum target, | |
| GLsizei samples, | |
| GLenum internal_format, | |
| GLsizei width, | |
| GLsizei height, | |
| ForcedMultisampleMode mode); | |
| void RenderbufferStorageMultisampleHelperAMD(GLenum target, | |
| GLsizei samples, | |
| GLsizei storageSamples, | |
| GLenum internal_format, | |
| GLsizei width, | |
| GLsizei height, | |
| ForcedMultisampleMode mode); | |
| bool RegenerateRenderbufferIfNeeded(Renderbuffer* renderbuffer); | |
| void SetCopyTextureResourceManagerForTest( | |
| CopyTextureCHROMIUMResourceManager* copy_texture_resource_manager) | |
| override { | |
| copy_texture_chromium_.reset(copy_texture_resource_manager); | |
| } | |
| void SetCopyTexImageBlitterForTest( | |
| CopyTexImageResourceManager* copy_tex_image_blit) override { | |
| copy_tex_image_blit_.reset(copy_tex_image_blit); | |
| } | |
| // ServiceFontManager::Client implementation. | |
| scoped_refptr<gpu::Buffer> GetShmBuffer(uint32_t shm_id); | |
| private: | |
| friend class ScopedFramebufferBinder; | |
| friend class ScopedResolvedFramebufferBinder; | |
| friend class ScopedFramebufferCopyBinder; | |
| friend class BackFramebuffer; | |
| friend class BackTexture; | |
| friend class ScopedBufferReattacher; | |
| enum FramebufferOperation { | |
| kFramebufferDiscard, | |
| kFramebufferInvalidate, | |
| kFramebufferInvalidateSub | |
| }; | |
| enum class BindIndexedBufferFunctionType { | |
| kBindBufferBase, | |
| kBindBufferRange | |
| }; | |
| // Helper class to ensure that GLES2DecoderImpl::Destroy() is always called | |
| // unless we specifically call OnSuccess(). | |
| class DestroyOnFailure { | |
| public: | |
| DestroyOnFailure(GLES2DecoderImpl* decoder) : decoder_(decoder) {} | |
| ~DestroyOnFailure() { | |
| if (!success_) | |
| decoder_->Destroy(has_context_); | |
| } | |
| void OnSuccess() { success_ = true; } | |
| void LoseContext() { has_context_ = false; } | |
| private: | |
| raw_ptr<GLES2DecoderImpl> decoder_ = nullptr; | |
| bool success_ = false; | |
| bool has_context_ = true; | |
| }; | |
| const char* GetCommandName(unsigned int command_id) const; | |
| // Initialize or re-initialize the shader translator. | |
| bool InitializeShaderTranslator(); | |
| void DestroyShaderTranslator(); | |
| GLint ComputeMaxSamples(); | |
| void UpdateCapabilities(); | |
| // Helpers for the glGen and glDelete functions. | |
| bool GenTexturesHelper(GLsizei n, const GLuint* client_ids); | |
| void DeleteTexturesHelper(GLsizei n, const volatile GLuint* client_ids); | |
| bool GenBuffersHelper(GLsizei n, const GLuint* client_ids); | |
| void DeleteBuffersHelper(GLsizei n, const volatile GLuint* client_ids); | |
| bool GenFramebuffersHelper(GLsizei n, const GLuint* client_ids); | |
| void DeleteFramebuffersHelper(GLsizei n, const volatile GLuint* client_ids); | |
| void FlushQueriesBeforeDeletingOrUnbindingFboWorkaround(); | |
| bool GenRenderbuffersHelper(GLsizei n, const GLuint* client_ids); | |
| void DeleteRenderbuffersHelper(GLsizei n, const volatile GLuint* client_ids); | |
| bool GenQueriesEXTHelper(GLsizei n, const GLuint* client_ids); | |
| void DeleteQueriesEXTHelper(GLsizei n, const volatile GLuint* client_ids); | |
| bool GenVertexArraysOESHelper(GLsizei n, const GLuint* client_ids); | |
| void DeleteVertexArraysOESHelper(GLsizei n, | |
| const volatile GLuint* client_ids); | |
| bool GenSamplersHelper(GLsizei n, const GLuint* client_ids); | |
| void DeleteSamplersHelper(GLsizei n, const volatile GLuint* client_ids); | |
| bool GenTransformFeedbacksHelper(GLsizei n, const GLuint* client_ids); | |
| void DeleteTransformFeedbacksHelper(GLsizei n, | |
| const volatile GLuint* client_ids); | |
| void DeleteSyncHelper(GLuint sync); | |
| // Workarounds | |
| void OnFboChanged() const; | |
| void ApplyRecreateFboWorkaroundIfNeeded() const; | |
| void OnUseFramebuffer() const; | |
| void UpdateFramebufferSRGB(Framebuffer* framebuffer); | |
| // TODO(gman): Cache these pointers? | |
| BufferManager* buffer_manager() { | |
| return group_->buffer_manager(); | |
| } | |
| RenderbufferManager* renderbuffer_manager() { | |
| return group_->renderbuffer_manager(); | |
| } | |
| FramebufferManager* framebuffer_manager() { | |
| return framebuffer_manager_.get(); | |
| } | |
| ProgramManager* program_manager() { | |
| return group_->program_manager(); | |
| } | |
| SamplerManager* sampler_manager() { | |
| return group_->sampler_manager(); | |
| } | |
| ShaderManager* shader_manager() { | |
| return group_->shader_manager(); | |
| } | |
| ShaderTranslatorCache* shader_translator_cache() { | |
| return group_->shader_translator_cache(); | |
| } | |
| const TextureManager* texture_manager() const { | |
| return group_->texture_manager(); | |
| } | |
| TextureManager* texture_manager() { | |
| return group_->texture_manager(); | |
| } | |
| VertexArrayManager* vertex_array_manager() { | |
| return vertex_array_manager_.get(); | |
| } | |
| MemoryTracker* memory_tracker() { | |
| return group_->memory_tracker(); | |
| } | |
| const gl::GLVersionInfo& gl_version_info() { | |
| return feature_info_->gl_version_info(); | |
| } | |
| // Creates a Texture for the given texture. | |
| TextureRef* CreateTexture( | |
| GLuint client_id, GLuint service_id) { | |
| return texture_manager()->CreateTexture(client_id, service_id); | |
| } | |
| // Gets the texture info for the given texture. Returns nullptr if none | |
| // exists. | |
| TextureRef* GetTexture(GLuint client_id) const { | |
| return texture_manager()->GetTexture(client_id); | |
| } | |
| // Deletes the texture info for the given texture. | |
| void RemoveTexture(GLuint client_id) { | |
| texture_manager()->RemoveTexture(client_id); | |
| } | |
| // Creates a Sampler for the given sampler. | |
| Sampler* CreateSampler( | |
| GLuint client_id, GLuint service_id) { | |
| return sampler_manager()->CreateSampler(client_id, service_id); | |
| } | |
| // Gets the sampler info for the given sampler. Returns nullptr if none | |
| // exists. | |
| Sampler* GetSampler(GLuint client_id) { | |
| return sampler_manager()->GetSampler(client_id); | |
| } | |
| // Deletes the sampler info for the given sampler. | |
| void RemoveSampler(GLuint client_id) { | |
| sampler_manager()->RemoveSampler(client_id); | |
| } | |
| // Creates a TransformFeedback for the given transformfeedback. | |
| TransformFeedback* CreateTransformFeedback( | |
| GLuint client_id, GLuint service_id) { | |
| return transform_feedback_manager_->CreateTransformFeedback( | |
| client_id, service_id); | |
| } | |
| // Gets the TransformFeedback info for the given transformfeedback. | |
| // Returns nullptr if none exists. | |
| TransformFeedback* GetTransformFeedback(GLuint client_id) { | |
| return transform_feedback_manager_->GetTransformFeedback(client_id); | |
| } | |
| // Deletes the TransformFeedback info for the given transformfeedback. | |
| void RemoveTransformFeedback(GLuint client_id) { | |
| transform_feedback_manager_->RemoveTransformFeedback(client_id); | |
| } | |
| // Get the size (in pixels) of the currently bound frame buffer (either FBO | |
| // or regular back buffer). | |
| gfx::Size GetBoundReadFramebufferSize(); | |
| gfx::Size GetBoundDrawFramebufferSize(); | |
| // Get the service side ID for the bound read framebuffer. | |
| // If it's back buffer, 0 is returned. | |
| GLuint GetBoundReadFramebufferServiceId(); | |
| // Get the service side ID for the bound draw framebuffer. | |
| // If it's back buffer, 0 is returned. | |
| GLuint GetBoundDrawFramebufferServiceId() const; | |
| // Get the format/type of the currently bound frame buffer (either FBO or | |
| // regular back buffer). | |
| // If the color image is a renderbuffer, returns 0 for type. | |
| GLenum GetBoundReadFramebufferTextureType(); | |
| GLenum GetBoundReadFramebufferInternalFormat(); | |
| bool IsBoundReadFramebufferMultisampledRenderbuffer(); | |
| // Get the i-th draw buffer's internal format/type from the bound framebuffer. | |
| // If no framebuffer is bound, or no image is attached, or the DrawBuffers | |
| // setting for that image is GL_NONE, return 0. | |
| GLenum GetBoundColorDrawBufferType(GLint drawbuffer_i); | |
| GLenum GetBoundColorDrawBufferInternalFormat(GLint drawbuffer_i); | |
| GLsizei GetBoundFramebufferSamples(GLenum target); | |
| // Return 0 if no depth attachment. | |
| GLenum GetBoundFramebufferDepthFormat(GLenum target); | |
| // Return 0 if no stencil attachment. | |
| GLenum GetBoundFramebufferStencilFormat(GLenum target); | |
| void MarkDrawBufferAsCleared(GLenum buffer, GLint drawbuffer_i); | |
| // Wrapper for CompressedTexImage{2|3}D commands. | |
| error::Error DoCompressedTexImage( | |
| GLenum target, | |
| GLint level, | |
| GLenum internal_format, | |
| GLsizei width, | |
| GLsizei height, | |
| GLsizei depth, | |
| GLint border, | |
| GLsizei image_size, | |
| const void* data, | |
| ContextState::Dimension dimension); | |
| // Wrapper for CompressedTexSubImage{2|3}D. | |
| error::Error DoCompressedTexSubImage( | |
| GLenum target, | |
| GLint level, | |
| GLint xoffset, | |
| GLint yoffset, | |
| GLint zoffset, | |
| GLsizei width, | |
| GLsizei height, | |
| GLsizei depth, | |
| GLenum format, | |
| GLsizei imageSize, | |
| const void* data, | |
| ContextState::Dimension dimension); | |
| // Validate if |format| is valid for CopyTex{Sub}Image functions. | |
| // If not, generate a GL error and return false. | |
| bool ValidateCopyTexFormat(const char* func_name, GLenum internal_format, | |
| GLenum read_format, GLenum read_type); | |
| // Wrapper for CopyTexImage2D. | |
| void DoCopyTexImage2D( | |
| GLenum target, | |
| GLint level, | |
| GLenum internal_format, | |
| GLint x, | |
| GLint y, | |
| GLsizei width, | |
| GLsizei height, | |
| GLint border); | |
| // Wrapper for CopyTexSubImage2D. | |
| void DoCopyTexSubImage2D( | |
| GLenum target, | |
| GLint level, | |
| GLint xoffset, | |
| GLint yoffset, | |
| GLint x, | |
| GLint y, | |
| GLsizei width, | |
| GLsizei height); | |
| // Wrapper for CopyTexSubImage3D. | |
| void DoCopyTexSubImage3D( | |
| GLenum target, | |
| GLint level, | |
| GLint xoffset, | |
| GLint yoffset, | |
| GLint zoffset, | |
| GLint x, | |
| GLint y, | |
| GLsizei width, | |
| GLsizei height); | |
| // Wrapper for glCopyBufferSubData. | |
| void DoCopyBufferSubData(GLenum readtarget, | |
| GLenum writetarget, | |
| GLintptr readoffset, | |
| GLintptr writeoffset, | |
| GLsizeiptr size); | |
| void DoCopyTextureCHROMIUM(GLuint source_id, | |
| GLint source_level, | |
| GLenum dest_target, | |
| GLuint dest_id, | |
| GLint dest_level, | |
| GLenum internal_format, | |
| GLenum dest_type, | |
| GLboolean unpack_flip_y, | |
| GLboolean unpack_premultiply_alpha, | |
| GLboolean unpack_unmultiply_alpha); | |
| void DoCopySubTextureCHROMIUM(GLuint source_id, | |
| GLint source_level, | |
| GLenum dest_target, | |
| GLuint dest_id, | |
| GLint dest_level, | |
| GLint xoffset, | |
| GLint yoffset, | |
| GLint x, | |
| GLint y, | |
| GLsizei width, | |
| GLsizei height, | |
| GLboolean unpack_flip_y, | |
| GLboolean unpack_premultiply_alpha, | |
| GLboolean unpack_unmultiply_alpha); | |
| // Helper for DoTexStorage2DEXT and DoTexStorage3D. | |
| void TexStorageImpl(GLenum target, | |
| GLsizei levels, | |
| GLenum internal_format, | |
| GLsizei width, | |
| GLsizei height, | |
| GLsizei depth, | |
| ContextState::Dimension dimension, | |
| const char* function_name); | |
| // Wrapper for TexStorage2DEXT. | |
| void DoTexStorage2DEXT(GLenum target, | |
| GLsizei levels, | |
| GLenum internal_format, | |
| GLsizei width, | |
| GLsizei height); | |
| // Wrapper for TexStorage3D. | |
| void DoTexStorage3D(GLenum target, | |
| GLsizei levels, | |
| GLenum internal_format, | |
| GLsizei width, | |
| GLsizei height, | |
| GLsizei depth); | |
| void DoTexStorage2DImageCHROMIUM(GLenum target, | |
| GLenum internal_format, | |
| GLenum buffer_usage, | |
| GLsizei width, | |
| GLsizei height); | |
| void DoCreateAndTexStorage2DSharedImageINTERNAL( | |
| GLuint client_id, | |
| const volatile GLbyte* mailbox); | |
| void DoBeginSharedImageAccessDirectCHROMIUM(GLuint client_id, GLenum mode); | |
| void DoEndSharedImageAccessDirectCHROMIUM(GLuint client_id); | |
| void DoCopySharedImageINTERNAL(GLint xoffset, | |
| GLint yoffset, | |
| GLint x, | |
| GLint y, | |
| GLsizei width, | |
| GLsizei height, | |
| const volatile GLbyte* mailboxes); | |
| void DoCopySharedImageToTextureINTERNAL(GLuint texture, | |
| GLenum target, | |
| GLuint internal_format, | |
| GLenum type, | |
| GLint src_x, | |
| GLint src_y, | |
| GLsizei width, | |
| GLsizei height, | |
| GLboolean is_dst_origin_top_left, | |
| const volatile GLbyte* src_mailbox); | |
| void DoTraceEndCHROMIUM(void); | |
| void DoDrawBuffersEXT(GLsizei count, const volatile GLenum* bufs); | |
| void DoLoseContextCHROMIUM(GLenum current, GLenum other); | |
| void DoFlushDriverCachesCHROMIUM(void); | |
| // Wrappers for ANGLE_shader_pixel_local_storage. | |
| void DoFramebufferMemorylessPixelLocalStorageANGLE(GLint plane, | |
| GLenum internalformat, | |
| GLbitfield usage); | |
| void DoFramebufferTexturePixelLocalStorageANGLE(GLint plane, | |
| GLuint backingtexture, | |
| GLint level, | |
| GLint layer, | |
| GLbitfield usage); | |
| void DoFramebufferPixelLocalClearValuefvANGLE(GLint plane, | |
| const volatile GLfloat* value); | |
| void DoFramebufferPixelLocalClearValueivANGLE(GLint plane, | |
| const volatile GLint* value); | |
| void DoFramebufferPixelLocalClearValueuivANGLE(GLint plane, | |
| const volatile GLuint* value); | |
| void DoBeginPixelLocalStorageANGLE(GLsizei n, const volatile GLenum* loadops); | |
| void DoEndPixelLocalStorageANGLE(GLsizei n, const volatile GLenum* storeops); | |
| void DoEndPixelLocalStorageImplicitANGLE(); | |
| void DoPixelLocalStorageBarrierANGLE(); | |
| void DoFramebufferPixelLocalStorageInterruptANGLE(); | |
| void DoFramebufferPixelLocalStorageRestoreANGLE(); | |
| void DoGetFramebufferPixelLocalStorageParameterfvANGLE(GLint plane, | |
| GLenum pname, | |
| GLfloat* params, | |
| GLsizei params_size); | |
| void DoGetFramebufferPixelLocalStorageParameterivANGLE(GLint plane, | |
| GLenum pname, | |
| GLint* params, | |
| GLsizei params_size); | |
| void DoGetFramebufferPixelLocalStorageParameteruivANGLE(GLint plane, | |
| GLenum pname, | |
| GLuint* params, | |
| GLsizei params_size); | |
| // Creates a Program for the given program. | |
| Program* CreateProgram(GLuint client_id, GLuint service_id) { | |
| return program_manager()->CreateProgram(client_id, service_id); | |
| } | |
| // Gets the program info for the given program. Returns nullptr if none | |
| // exists. | |
| Program* GetProgram(GLuint client_id) { | |
| return program_manager()->GetProgram(client_id); | |
| } | |
| #if defined(NDEBUG) | |
| void LogClientServiceMapping( | |
| const char* /* function_name */, | |
| GLuint /* client_id */, | |
| GLuint /* service_id */) { | |
| } | |
| template<typename T> | |
| void LogClientServiceForInfo( | |
| T* /* info */, GLuint /* client_id */, const char* /* function_name */) { | |
| } | |
| #else | |
| void LogClientServiceMapping( | |
| const char* function_name, GLuint client_id, GLuint service_id) { | |
| if (service_logging_) { | |
| VLOG(1) << "[" << logger_.GetLogPrefix() << "] " << function_name | |
| << ": client_id = " << client_id | |
| << ", service_id = " << service_id; | |
| } | |
| } | |
| template<typename T> | |
| void LogClientServiceForInfo( | |
| T* info, GLuint client_id, const char* function_name) { | |
| if (info) { | |
| LogClientServiceMapping(function_name, client_id, info->service_id()); | |
| } | |
| } | |
| #endif | |
| // Gets the program info for the given program. If it's not a program | |
| // generates a GL error. Returns nullptr if not program. | |
| Program* GetProgramInfoNotShader( | |
| GLuint client_id, const char* function_name) { | |
| Program* program = GetProgram(client_id); | |
| if (!program) { | |
| if (GetShader(client_id)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, function_name, "shader passed for program"); | |
| } else { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "unknown program"); | |
| } | |
| } | |
| LogClientServiceForInfo(program, client_id, function_name); | |
| return program; | |
| } | |
| // Creates a Shader for the given shader. | |
| Shader* CreateShader( | |
| GLuint client_id, | |
| GLuint service_id, | |
| GLenum shader_type) { | |
| return shader_manager()->CreateShader( | |
| client_id, service_id, shader_type); | |
| } | |
| // Gets the shader info for the given shader. Returns nullptr if none exists. | |
| Shader* GetShader(GLuint client_id) { | |
| return shader_manager()->GetShader(client_id); | |
| } | |
| // Gets the shader info for the given shader. If it's not a shader generates a | |
| // GL error. Returns nullptr if not shader. | |
| Shader* GetShaderInfoNotProgram( | |
| GLuint client_id, const char* function_name) { | |
| Shader* shader = GetShader(client_id); | |
| if (!shader) { | |
| if (GetProgram(client_id)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, function_name, "program passed for shader"); | |
| } else { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, function_name, "unknown shader"); | |
| } | |
| } | |
| LogClientServiceForInfo(shader, client_id, function_name); | |
| return shader; | |
| } | |
| // Creates a buffer info for the given buffer. | |
| void CreateBuffer(GLuint client_id, GLuint service_id) { | |
| return buffer_manager()->CreateBuffer(client_id, service_id); | |
| } | |
| // Gets the buffer info for the given buffer. | |
| Buffer* GetBuffer(GLuint client_id) { | |
| Buffer* buffer = buffer_manager()->GetBuffer(client_id); | |
| return buffer; | |
| } | |
| // Creates a framebuffer info for the given framebuffer. | |
| void CreateFramebuffer(GLuint client_id, GLuint service_id) { | |
| return framebuffer_manager()->CreateFramebuffer(client_id, service_id); | |
| } | |
| // Gets the framebuffer info for the given framebuffer. | |
| Framebuffer* GetFramebuffer(GLuint client_id) { | |
| return framebuffer_manager()->GetFramebuffer(client_id); | |
| } | |
| // Removes the framebuffer info for the given framebuffer. | |
| void RemoveFramebuffer(GLuint client_id) { | |
| framebuffer_manager()->RemoveFramebuffer(client_id); | |
| } | |
| // Creates a renderbuffer info for the given renderbuffer. | |
| void CreateRenderbuffer(GLuint client_id, GLuint service_id) { | |
| return renderbuffer_manager()->CreateRenderbuffer( | |
| client_id, service_id); | |
| } | |
| // Gets the renderbuffer info for the given renderbuffer. | |
| Renderbuffer* GetRenderbuffer(GLuint client_id) { | |
| return renderbuffer_manager()->GetRenderbuffer(client_id); | |
| } | |
| // Removes the renderbuffer info for the given renderbuffer. | |
| void RemoveRenderbuffer(GLuint client_id) { | |
| renderbuffer_manager()->RemoveRenderbuffer(client_id); | |
| } | |
| // Gets the vertex attrib manager for the given vertex array. | |
| VertexAttribManager* GetVertexAttribManager(GLuint client_id) { | |
| VertexAttribManager* info = | |
| vertex_array_manager()->GetVertexAttribManager(client_id); | |
| return info; | |
| } | |
| // Removes the vertex attrib manager for the given vertex array. | |
| void RemoveVertexAttribManager(GLuint client_id) { | |
| vertex_array_manager()->RemoveVertexAttribManager(client_id); | |
| } | |
| // Creates a vertex attrib manager for the given vertex array. | |
| scoped_refptr<VertexAttribManager> CreateVertexAttribManager( | |
| GLuint client_id, | |
| GLuint service_id, | |
| bool client_visible) { | |
| return vertex_array_manager()->CreateVertexAttribManager( | |
| client_id, service_id, group_->max_vertex_attribs(), client_visible, | |
| feature_info_->IsWebGL2OrES3Context()); | |
| } | |
| void DoBindAttribLocation(GLuint client_id, | |
| GLuint index, | |
| const std::string& name); | |
| error::Error DoBindFragDataLocation(GLuint program_id, | |
| GLuint colorName, | |
| const std::string& name); | |
| error::Error DoBindFragDataLocationIndexed(GLuint program_id, | |
| GLuint colorName, | |
| GLuint index, | |
| const std::string& name); | |
| void DoBindUniformLocationCHROMIUM(GLuint client_id, | |
| GLint location, | |
| const std::string& name); | |
| error::Error GetAttribLocationHelper(GLuint client_id, | |
| uint32_t location_shm_id, | |
| uint32_t location_shm_offset, | |
| const std::string& name_str); | |
| error::Error GetUniformLocationHelper(GLuint client_id, | |
| uint32_t location_shm_id, | |
| uint32_t location_shm_offset, | |
| const std::string& name_str); | |
| error::Error GetFragDataLocationHelper(GLuint client_id, | |
| uint32_t location_shm_id, | |
| uint32_t location_shm_offset, | |
| const std::string& name_str); | |
| error::Error GetFragDataIndexHelper(GLuint program_id, | |
| uint32_t index_shm_id, | |
| uint32_t index_shm_offset, | |
| const std::string& name_str); | |
| // Wrapper for glShaderSource. | |
| void DoShaderSource( | |
| GLuint client_id, GLsizei count, const char** data, const GLint* length); | |
| // Wrapper for glTransformFeedbackVaryings. | |
| void DoTransformFeedbackVaryings( | |
| GLuint client_program_id, GLsizei count, const char* const* varyings, | |
| GLenum buffer_mode); | |
| // Clear any textures used by the current program. | |
| bool ClearUnclearedTextures(); | |
| // Clears any uncleared attachments attached to the given frame buffer. | |
| // Returns false if there was a generated GL error. | |
| bool ClearUnclearedAttachments(GLenum target, Framebuffer* framebuffer); | |
| // overridden from GLES2Decoder | |
| bool ClearLevel(Texture* texture, | |
| unsigned target, | |
| int level, | |
| unsigned format, | |
| unsigned type, | |
| int xoffset, | |
| int yoffset, | |
| int width, | |
| int height) override; | |
| // Helper function for ClearLevel that attempts to clear using a glClear call | |
| // to a temporary FBO, rather than using glTexSubImage2D. | |
| bool ClearLevelUsingGL(Texture* texture, | |
| uint32_t channels, | |
| unsigned target, | |
| int level, | |
| int xoffset, | |
| int yoffset, | |
| int width, | |
| int height); | |
| // overridden from GLES2Decoder | |
| bool ClearCompressedTextureLevel(Texture* texture, | |
| unsigned target, | |
| int level, | |
| unsigned format, | |
| int width, | |
| int height) override; | |
| bool ClearCompressedTextureLevel3D(Texture* texture, | |
| unsigned target, | |
| int level, | |
| unsigned format, | |
| int width, | |
| int height, | |
| int depth) override; | |
| bool IsCompressedTextureFormat(unsigned format) override; | |
| // overridden from GLES2Decoder | |
| bool ClearLevel3D(Texture* texture, | |
| unsigned target, | |
| int level, | |
| unsigned format, | |
| unsigned type, | |
| int width, | |
| int height, | |
| int depth) override; | |
| // Restore all GL state that affects clearing. | |
| void RestoreClearState(); | |
| // Remembers the state of some capabilities. | |
| // Returns: true if glEnable/glDisable should actually be called. | |
| bool SetCapabilityState(GLenum cap, bool enabled); | |
| // Check that the currently bound read framebuffer's color image | |
| // isn't the target texture of the glCopyTex{Sub}Image{2D|3D}. | |
| bool FormsTextureCopyingFeedbackLoop( | |
| TextureRef* texture, | |
| GLint level, | |
| GLint layer); | |
| // Check if a framebuffer meets our requirements. | |
| // Generates |gl_error| if the framebuffer is incomplete. | |
| bool CheckFramebufferValid( | |
| Framebuffer* framebuffer, | |
| GLenum target, | |
| GLenum gl_error, | |
| const char* func_name); | |
| bool CheckBoundDrawFramebufferValid(const char* func_name, | |
| bool check_float_blending = false); | |
| // Generates |gl_error| if the bound read fbo is incomplete. | |
| bool CheckBoundReadFramebufferValid(const char* func_name, GLenum gl_error); | |
| // This is only used by DoBlitFramebufferCHROMIUM which operates read/draw | |
| // framebuffer at the same time. | |
| bool CheckBoundFramebufferValid(const char* func_name); | |
| // Checks if the current program exists and is valid. If not generates the | |
| // appropriate GL error. Returns true if the current program is in a usable | |
| // state. | |
| bool CheckCurrentProgram(const char* function_name); | |
| // Checks if the current program exists and is valid and that location is not | |
| // -1. If the current program is not valid generates the appropriate GL | |
| // error. Returns true if the current program is in a usable state and | |
| // location is not -1. | |
| bool CheckCurrentProgramForUniform(GLint location, const char* function_name); | |
| // Helper for CheckDrawingFeedbackLoops. Returns true if the attachment is | |
| // the same one where it samples from during drawing. | |
| bool CheckDrawingFeedbackLoopsHelper( | |
| const Framebuffer::Attachment* attachment, | |
| TextureRef* texture_ref, | |
| const char* function_name); | |
| bool SupportsDrawBuffers() const; | |
| // Checks if a draw buffer's format and its corresponding fragment shader | |
| // output's type are compatible, i.e., a signed integer typed variable is | |
| // incompatible with a float or unsigned integer buffer. | |
| // If incompaticle, generates an INVALID_OPERATION to avoid undefined buffer | |
| // contents and return false. | |
| // Otherwise, filter out the draw buffers that are not written to but are not | |
| // NONE through DrawBuffers, to be on the safe side. Return true. | |
| bool ValidateAndAdjustDrawBuffers(const char* function_name); | |
| // Filter out the draw buffers that have no images attached but are not NONE | |
| // through DrawBuffers, to be on the safe side. | |
| void AdjustDrawBuffers(); | |
| // Checks if all active uniform blocks in the current program are backed by | |
| // a buffer of sufficient size. | |
| // If not, generates an INVALID_OPERATION to avoid undefined behavior in | |
| // shader execution and return false. | |
| bool ValidateUniformBlockBackings(const char* function_name); | |
| // Checks if |api_type| is valid for the given uniform | |
| // If the api type is not valid generates the appropriate GL | |
| // error. Returns true if |api_type| is valid for the uniform | |
| bool CheckUniformForApiType(const Program::UniformInfo* info, | |
| const char* function_name, | |
| UniformApiType api_type); | |
| // Gets the type of a uniform for a location in the current program. Sets GL | |
| // errors if the current program is not valid. Returns true if the current | |
| // program is valid and the location exists. Adjusts count so it | |
| // does not overflow the uniform. | |
| bool PrepForSetUniformByLocation(GLint fake_location, | |
| const char* function_name, | |
| UniformApiType api_type, | |
| GLint* real_location, | |
| GLenum* type, | |
| GLsizei* count); | |
| // Gets the service id for any simulated backbuffer fbo. | |
| GLuint GetBackbufferServiceId() const; | |
| // Helper for glGetBooleanv, glGetFloatv and glGetIntegerv. Returns | |
| // false if pname is unhandled. | |
| bool GetHelper(GLenum pname, GLint* params, GLsizei* num_written); | |
| // Helper for glGetVertexAttrib | |
| void GetVertexAttribHelper( | |
| const VertexAttrib* attrib, GLenum pname, GLint* param); | |
| // Wrapper for glActiveTexture | |
| void DoActiveTexture(GLenum texture_unit); | |
| // Wrapper for glAttachShader | |
| void DoAttachShader(GLuint client_program_id, GLint client_shader_id); | |
| // Wrapper for glBindBuffer since we need to track the current targets. | |
| void DoBindBuffer(GLenum target, GLuint buffer); | |
| // Wrapper for glBindBufferBase since we need to track the current targets. | |
| void DoBindBufferBase(GLenum target, GLuint index, GLuint buffer); | |
| // Wrapper for glBindBufferRange since we need to track the current targets. | |
| void DoBindBufferRange(GLenum target, GLuint index, GLuint buffer, | |
| GLintptr offset, GLsizeiptr size); | |
| // Helper for DoBindBufferBase and DoBindBufferRange. | |
| void BindIndexedBufferImpl(GLenum target, GLuint index, GLuint buffer, | |
| GLintptr offset, GLsizeiptr size, | |
| BindIndexedBufferFunctionType function_type, | |
| const char* function_name); | |
| // Wrapper for glBindFramebuffer since we need to track the current targets. | |
| void DoBindFramebuffer(GLenum target, GLuint framebuffer); | |
| // Wrapper for glBindRenderbuffer since we need to track the current targets. | |
| void DoBindRenderbuffer(GLenum target, GLuint renderbuffer); | |
| // Updates any targets to which `client_id` is bound to be bound to `texture`. | |
| void UpdateTextureBinding(GLenum target, | |
| GLuint client_id, | |
| TextureRef* texture_ref); | |
| // Wrapper for glBindTexture since we need to track the current targets. | |
| void DoBindTexture(GLenum target, GLuint texture); | |
| // Wrapper for glBindSampler since we need to track the current targets. | |
| void DoBindSampler(GLuint unit, GLuint sampler); | |
| // Wrapper for glBindTransformFeedback since we need to emulate ES3 behaviors | |
| // for BindBufferRange on Desktop GL lower than 4.2. | |
| void DoBindTransformFeedback(GLenum target, GLuint transform_feedback); | |
| // Wrapper for glBeginTransformFeedback. | |
| void DoBeginTransformFeedback(GLenum primitive_mode); | |
| // Wrapper for glEndTransformFeedback. | |
| void DoEndTransformFeedback(); | |
| // Wrapper for glPauseTransformFeedback. | |
| void DoPauseTransformFeedback(); | |
| // Wrapper for glResumeTransformFeedback. | |
| void DoResumeTransformFeedback(); | |
| // Wrapper for glBindVertexArrayOES | |
| void DoBindVertexArrayOES(GLuint array); | |
| void EmulateVertexArrayState(); | |
| // Wrapper for glBlitFramebufferCHROMIUM. | |
| void DoBlitFramebufferCHROMIUM( | |
| GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, | |
| GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, | |
| GLbitfield mask, GLenum filter); | |
| // Wrapper for glBufferSubData. | |
| void DoBufferSubData( | |
| GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid * data); | |
| // Wrapper for glCheckFramebufferStatus | |
| GLenum DoCheckFramebufferStatus(GLenum target); | |
| // Wrapper for glClear*() | |
| error::Error DoClear(GLbitfield mask); | |
| void DoClearBufferiv(GLenum buffer, | |
| GLint drawbuffer, | |
| const volatile GLint* value); | |
| void DoClearBufferuiv(GLenum buffer, | |
| GLint drawbuffer, | |
| const volatile GLuint* value); | |
| void DoClearBufferfv(GLenum buffer, | |
| GLint drawbuffer, | |
| const volatile GLfloat* value); | |
| void DoClearBufferfi( | |
| GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); | |
| // Wrappers for various state. | |
| void DoDepthRangef(GLclampf znear, GLclampf zfar); | |
| void DoSampleCoverage(GLclampf value, GLboolean invert); | |
| // Wrapper for glCompileShader. | |
| void DoCompileShader(GLuint shader); | |
| // Wrapper for glDetachShader | |
| void DoDetachShader(GLuint client_program_id, GLint client_shader_id); | |
| // Wrapper for glDisable | |
| void DoDisable(GLenum cap); | |
| // Wrapper for glDisableiOES | |
| void DoDisableiOES(GLenum target, GLuint index); | |
| // Wrapper for glDisableVertexAttribArray. | |
| void DoDisableVertexAttribArray(GLuint index); | |
| // Wrapper for glDiscardFramebufferEXT, since we need to track undefined | |
| // attachments. | |
| void DoDiscardFramebufferEXT(GLenum target, | |
| GLsizei count, | |
| const volatile GLenum* attachments); | |
| void DoInvalidateFramebuffer(GLenum target, | |
| GLsizei count, | |
| const volatile GLenum* attachments); | |
| void DoInvalidateSubFramebuffer(GLenum target, | |
| GLsizei count, | |
| const volatile GLenum* attachments, | |
| GLint x, | |
| GLint y, | |
| GLsizei width, | |
| GLsizei height); | |
| // Helper for DoDiscardFramebufferEXT, DoInvalidate{Sub}Framebuffer. | |
| void InvalidateFramebufferImpl(GLenum target, | |
| GLsizei count, | |
| const volatile GLenum* attachments, | |
| GLint x, | |
| GLint y, | |
| GLsizei width, | |
| GLsizei height, | |
| const char* function_name, | |
| FramebufferOperation op); | |
| // Wrapper for glEnable | |
| void DoEnable(GLenum cap); | |
| // Wrapper for glEnableiOES | |
| void DoEnableiOES(GLenum target, GLuint index); | |
| // Wrapper for glEnableVertexAttribArray. | |
| void DoEnableVertexAttribArray(GLuint index); | |
| // Wrapper for glFinish. | |
| void DoFinish(); | |
| // Wrapper for glFlush. | |
| void DoFlush(); | |
| // Wrapper for glFramebufferParameteri. | |
| void DoFramebufferParameteri(GLenum target, GLenum pname, GLint param); | |
| // Wrapper for glFramebufferRenderbufffer. | |
| void DoFramebufferRenderbuffer( | |
| GLenum target, GLenum attachment, GLenum renderbuffertarget, | |
| GLuint renderbuffer); | |
| // Wrapper for glFramebufferTexture2D. | |
| void DoFramebufferTexture2D( | |
| GLenum target, GLenum attachment, GLenum textarget, GLuint texture, | |
| GLint level); | |
| // Wrapper for glFramebufferTexture2DMultisampleEXT. | |
| void DoFramebufferTexture2DMultisample( | |
| GLenum target, GLenum attachment, GLenum textarget, | |
| GLuint texture, GLint level, GLsizei samples); | |
| // Common implementation for both DoFramebufferTexture2D wrappers. | |
| void DoFramebufferTexture2DCommon(const char* name, | |
| GLenum target, GLenum attachment, GLenum textarget, | |
| GLuint texture, GLint level, GLsizei samples); | |
| // Wrapper for glFramebufferTextureLayer. | |
| void DoFramebufferTextureLayer( | |
| GLenum target, GLenum attachment, GLuint texture, GLint level, | |
| GLint layer); | |
| // Wrapper for glFramebufferTextureLayer. | |
| void DoFramebufferTextureMultiviewOVR(GLenum target, | |
| GLenum attachment, | |
| GLuint texture, | |
| GLint level, | |
| GLint base_view_index, | |
| GLsizei num_views); | |
| // Wrapper for glGenerateMipmap | |
| void DoGenerateMipmap(GLenum target); | |
| void RecreateMipmapLevelsBeforeGenerate(TextureRef* texture_ref, | |
| GLenum target); | |
| // Helper for DoGetBooleanv, Floatv, and Intergerv to adjust pname | |
| // to account for different pname values defined in different extension | |
| // variants. | |
| GLenum AdjustGetPname(GLenum pname); | |
| // Wrapper for DoGetBooleanv. | |
| void DoGetBooleanv(GLenum pname, GLboolean* params, GLsizei params_size); | |
| // Wrapper for DoGetFloatv. | |
| void DoGetFloatv(GLenum pname, GLfloat* params, GLsizei params_size); | |
| // Wrapper for glGetFramebufferAttachmentParameteriv. | |
| void DoGetFramebufferAttachmentParameteriv(GLenum target, | |
| GLenum attachment, | |
| GLenum pname, | |
| GLint* params, | |
| GLsizei params_size); | |
| // Wrapper for glGetInteger64v. | |
| void DoGetInteger64v(GLenum pname, GLint64* params, GLsizei params_size); | |
| // Wrapper for glGetIntegerv. | |
| void DoGetIntegerv(GLenum pname, GLint* params, GLsizei params_size); | |
| // Helper for DoGetBooleani_v, DoGetIntegeri_v and DoGetInteger64i_v. | |
| template <typename TYPE> | |
| void GetIndexedIntegerImpl( | |
| const char* function_name, GLenum target, GLuint index, TYPE* data); | |
| // Wrapper for glGetBooleani_v. | |
| void DoGetBooleani_v(GLenum target, | |
| GLuint index, | |
| GLboolean* params, | |
| GLsizei params_size); | |
| // Wrapper for glGetIntegeri_v. | |
| void DoGetIntegeri_v(GLenum target, | |
| GLuint index, | |
| GLint* params, | |
| GLsizei params_size); | |
| // Wrapper for glGetInteger64i_v. | |
| void DoGetInteger64i_v(GLenum target, | |
| GLuint index, | |
| GLint64* params, | |
| GLsizei params_size); | |
| // Gets the max value in a range in a buffer. | |
| GLuint DoGetMaxValueInBufferCHROMIUM( | |
| GLuint buffer_id, GLsizei count, GLenum type, GLuint offset); | |
| // Wrapper for glGetBufferParameteri64v. | |
| void DoGetBufferParameteri64v(GLenum target, | |
| GLenum pname, | |
| GLint64* params, | |
| GLsizei params_size); | |
| // Wrapper for glGetBufferParameteriv. | |
| void DoGetBufferParameteriv(GLenum target, | |
| GLenum pname, | |
| GLint* params, | |
| GLsizei params_size); | |
| // Wrapper for glGetProgramiv. | |
| void DoGetProgramiv(GLuint program_id, | |
| GLenum pname, | |
| GLint* params, | |
| GLsizei params_size); | |
| // Wrapper for glRenderbufferParameteriv. | |
| void DoGetRenderbufferParameteriv(GLenum target, | |
| GLenum pname, | |
| GLint* params, | |
| GLsizei params_size); | |
| // Wrappers for glGetSamplerParameter. | |
| void DoGetSamplerParameterfv(GLuint client_id, | |
| GLenum pname, | |
| GLfloat* params, | |
| GLsizei params_size); | |
| void DoGetSamplerParameteriv(GLuint client_id, | |
| GLenum pname, | |
| GLint* params, | |
| GLsizei params_size); | |
| // Wrapper for glGetShaderiv | |
| void DoGetShaderiv(GLuint shader, | |
| GLenum pname, | |
| GLint* params, | |
| GLsizei params_size); | |
| // Wrapper for glGetSynciv. | |
| void DoGetSynciv(GLuint sync_id, | |
| GLenum pname, | |
| GLsizei num_values, | |
| GLsizei* length, | |
| GLint* values); | |
| // Helper for DoGetTexParameter{f|i}v. | |
| void GetTexParameterImpl( | |
| GLenum target, GLenum pname, GLfloat* fparams, GLint* iparams, | |
| const char* function_name); | |
| // Wrappers for glGetTexParameter. | |
| void DoGetTexParameterfv(GLenum target, | |
| GLenum pname, | |
| GLfloat* params, | |
| GLsizei params_size); | |
| void DoGetTexParameteriv(GLenum target, | |
| GLenum pname, | |
| GLint* params, | |
| GLsizei params_size); | |
| // Wrappers for glGetVertexAttrib. | |
| template <typename T> | |
| void DoGetVertexAttribImpl(GLuint index, GLenum pname, T* params); | |
| void DoGetVertexAttribfv(GLuint index, | |
| GLenum pname, | |
| GLfloat* params, | |
| GLsizei params_size); | |
| void DoGetVertexAttribiv(GLuint index, | |
| GLenum pname, | |
| GLint* params, | |
| GLsizei params_size); | |
| void DoGetVertexAttribIiv(GLuint index, | |
| GLenum pname, | |
| GLint* params, | |
| GLsizei params_size); | |
| void DoGetVertexAttribIuiv(GLuint index, | |
| GLenum pname, | |
| GLuint* params, | |
| GLsizei params_size); | |
| // Wrappers for glIsXXX functions. | |
| bool DoIsEnabled(GLenum cap); | |
| bool DoIsBuffer(GLuint client_id); | |
| bool DoIsFramebuffer(GLuint client_id); | |
| bool DoIsProgram(GLuint client_id); | |
| bool DoIsRenderbuffer(GLuint client_id); | |
| bool DoIsShader(GLuint client_id); | |
| bool DoIsTexture(GLuint client_id); | |
| bool DoIsSampler(GLuint client_id); | |
| bool DoIsTransformFeedback(GLuint client_id); | |
| bool DoIsVertexArrayOES(GLuint client_id); | |
| bool DoIsSync(GLuint client_id); | |
| bool DoIsEnablediOES(GLenum target, GLuint index); | |
| void DoLineWidth(GLfloat width); | |
| // Wrapper for glLinkProgram | |
| void DoLinkProgram(GLuint program); | |
| void DoMultiDrawBeginCHROMIUM(GLsizei drawcount); | |
| void DoMultiDrawEndCHROMIUM(); | |
| // Wrapper for glReadBuffer | |
| void DoReadBuffer(GLenum src); | |
| // Wrapper for glRenderbufferStorage. | |
| void DoRenderbufferStorage( | |
| GLenum target, GLenum internalformat, GLsizei width, GLsizei height); | |
| // Handler for glRenderbufferStorageMultisampleCHROMIUM. | |
| void DoRenderbufferStorageMultisampleCHROMIUM( | |
| GLenum target, GLsizei samples, GLenum internalformat, | |
| GLsizei width, GLsizei height); | |
| // Handler for glRenderbufferStorageMultisampleAdvancedAMD. | |
| void DoRenderbufferStorageMultisampleAdvancedAMD(GLenum target, | |
| GLsizei samples, | |
| GLsizei storageSamples, | |
| GLenum internalformat, | |
| GLsizei width, | |
| GLsizei height); | |
| // Handler for glRenderbufferStorageMultisampleEXT | |
| // (multisampled_render_to_texture). | |
| void DoRenderbufferStorageMultisampleEXT( | |
| GLenum target, GLsizei samples, GLenum internalformat, | |
| GLsizei width, GLsizei height); | |
| // Wrapper for glFenceSync. | |
| GLsync DoFenceSync(GLenum condition, GLbitfield flags); | |
| GLsizei InternalFormatSampleCountsHelper( | |
| GLenum target, | |
| GLenum format, | |
| std::vector<GLint>* out_sample_counts); | |
| // Common validation for multisample extensions. | |
| bool ValidateRenderbufferStorageMultisample(GLsizei samples, | |
| GLenum internalformat, | |
| GLsizei width, | |
| GLsizei height); | |
| // validation for multisample AMD extension. | |
| bool ValidateRenderbufferStorageMultisampleAMD(GLsizei samples, | |
| GLsizei storageSamples, | |
| GLenum internalformat, | |
| GLsizei width, | |
| GLsizei height); | |
| // Wrapper for glReleaseShaderCompiler. | |
| void DoReleaseShaderCompiler(); | |
| // Wrappers for glSamplerParameter functions. | |
| void DoSamplerParameterf(GLuint client_id, GLenum pname, GLfloat param); | |
| void DoSamplerParameteri(GLuint client_id, GLenum pname, GLint param); | |
| void DoSamplerParameterfv(GLuint client_id, | |
| GLenum pname, | |
| const volatile GLfloat* params); | |
| void DoSamplerParameteriv(GLuint client_id, | |
| GLenum pname, | |
| const volatile GLint* params); | |
| // Wrappers for glTexParameter functions. | |
| bool CheckTexParameterBaseLevel(TextureRef* texture, | |
| GLenum pname, | |
| GLint param); | |
| void DoTexParameterf(GLenum target, GLenum pname, GLfloat param); | |
| void DoTexParameteri(GLenum target, GLenum pname, GLint param); | |
| void DoTexParameterfv(GLenum target, | |
| GLenum pname, | |
| const volatile GLfloat* params); | |
| void DoTexParameteriv(GLenum target, | |
| GLenum pname, | |
| const volatile GLint* params); | |
| // Wrappers for glUniform1i and glUniform1iv as according to the GLES2 | |
| // spec only these 2 functions can be used to set sampler uniforms. | |
| void DoUniform1i(GLint fake_location, GLint v0); | |
| void DoUniform1iv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLint* value); | |
| void DoUniform2iv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLint* value); | |
| void DoUniform3iv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLint* value); | |
| void DoUniform4iv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLint* value); | |
| void DoUniform1ui(GLint fake_location, GLuint v0); | |
| void DoUniform1uiv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLuint* value); | |
| void DoUniform2uiv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLuint* value); | |
| void DoUniform3uiv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLuint* value); | |
| void DoUniform4uiv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLuint* value); | |
| // Wrappers for glUniformfv because some drivers don't correctly accept | |
| // bool uniforms. | |
| void DoUniform1fv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLfloat* value); | |
| void DoUniform2fv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLfloat* value); | |
| void DoUniform3fv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLfloat* value); | |
| void DoUniform4fv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLfloat* value); | |
| void DoUniformMatrix2fv(GLint fake_location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const volatile GLfloat* value); | |
| void DoUniformMatrix3fv(GLint fake_location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const volatile GLfloat* value); | |
| void DoUniformMatrix4fv(GLint fake_location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const volatile GLfloat* value); | |
| void DoUniformMatrix2x3fv(GLint fake_location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const volatile GLfloat* value); | |
| void DoUniformMatrix2x4fv(GLint fake_location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const volatile GLfloat* value); | |
| void DoUniformMatrix3x2fv(GLint fake_location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const volatile GLfloat* value); | |
| void DoUniformMatrix3x4fv(GLint fake_location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const volatile GLfloat* value); | |
| void DoUniformMatrix4x2fv(GLint fake_location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const volatile GLfloat* value); | |
| void DoUniformMatrix4x3fv(GLint fake_location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const volatile GLfloat* value); | |
| template <typename T> | |
| bool SetVertexAttribValue( | |
| const char* function_name, GLuint index, const T* value); | |
| // Wrappers for glVertexAttrib?? | |
| void DoVertexAttrib1f(GLuint index, GLfloat v0); | |
| void DoVertexAttrib2f(GLuint index, GLfloat v0, GLfloat v1); | |
| void DoVertexAttrib3f(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2); | |
| void DoVertexAttrib4f( | |
| GLuint index, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); | |
| void DoVertexAttrib1fv(GLuint index, const volatile GLfloat* v); | |
| void DoVertexAttrib2fv(GLuint index, const volatile GLfloat* v); | |
| void DoVertexAttrib3fv(GLuint index, const volatile GLfloat* v); | |
| void DoVertexAttrib4fv(GLuint index, const volatile GLfloat* v); | |
| void DoVertexAttribI4i(GLuint index, GLint v0, GLint v1, GLint v2, GLint v3); | |
| void DoVertexAttribI4iv(GLuint index, const volatile GLint* v); | |
| void DoVertexAttribI4ui( | |
| GLuint index, GLuint v0, GLuint v1, GLuint v2, GLuint v3); | |
| void DoVertexAttribI4uiv(GLuint index, const volatile GLuint* v); | |
| // Wrapper for glViewport | |
| void DoViewport(GLint x, GLint y, GLsizei width, GLsizei height); | |
| // Wrapper for glScissor | |
| void DoScissor(GLint x, GLint y, GLsizei width, GLsizei height); | |
| // Wrapper for glUseProgram | |
| void DoUseProgram(GLuint program); | |
| // Wrapper for glValidateProgram. | |
| void DoValidateProgram(GLuint program_client_id); | |
| void DoInsertEventMarkerEXT(GLsizei length, const GLchar* marker); | |
| void DoPushGroupMarkerEXT(GLsizei length, const GLchar* group); | |
| void DoPopGroupMarkerEXT(void); | |
| // Wrapper for ContextVisibilityHintCHROMIUM. | |
| void DoContextVisibilityHintCHROMIUM(GLboolean visibility); | |
| // Gets the number of values that will be returned by glGetXXX. Returns | |
| // false if pname is unknown. | |
| bool GetNumValuesReturnedForGLGet(GLenum pname, GLsizei* num_values); | |
| // Checks if every attribute's type set by vertexAttrib API match | |
| // the type of corresponding attribute in vertex shader. | |
| bool AttribsTypeMatch(); | |
| // Verifies that front/back stencil settings match, per WebGL specification: | |
| // https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.11 | |
| bool ValidateStencilStateForDraw(const char* function_name); | |
| // Checks if the current program and vertex attributes are valid for drawing. | |
| bool IsDrawValid(const char* function_name, | |
| GLuint max_vertex_accessed, | |
| bool instanced, | |
| GLsizei primcount, | |
| GLint basevertex, | |
| GLuint baseinstance); | |
| // Returns true if successful, simulated will be true if attrib0 was | |
| // simulated. | |
| void RestoreStateForAttrib(GLuint attrib, bool restore_array_binding); | |
| void DoWindowRectanglesEXT(GLenum mode, GLsizei n, const volatile GLint* box); | |
| void DoSetReadbackBufferShadowAllocationINTERNAL(GLuint buffer_id, | |
| GLuint shm_id, | |
| GLuint shm_offset, | |
| GLuint size); | |
| // Returns false if a GL error occurred. textures_set is always modified | |
| // appropriately to indicate whether textures were set, even on failure. | |
| bool PrepareTexturesForRender(bool* textures_set, const char* function_name); | |
| void RestoreStateForTextures(); | |
| // Having extra base vertex and base instance parameters and run-time if else | |
| // for heavily called DoMultiDrawArrays/DoMultiDrawElements caused | |
| // performance regression, thus use non-type template draw functions | |
| enum class DrawArraysOption { Default = 0, UseBaseInstance }; | |
| enum class DrawElementsOption { Default = 0, UseBaseVertexBaseInstance }; | |
| template <DrawArraysOption option> | |
| bool CheckMultiDrawArraysVertices(const char* function_name, | |
| bool instanced, | |
| const GLint* firsts, | |
| const GLsizei* counts, | |
| const GLsizei* primcounts, | |
| const GLuint* baseinstances, | |
| GLsizei drawcount, | |
| GLuint* total_max_vertex_accessed, | |
| GLsizei* total_max_primcount); | |
| template <DrawElementsOption option> | |
| bool CheckMultiDrawElementsVertices(const char* function_name, | |
| bool instanced, | |
| const GLsizei* counts, | |
| GLenum type, | |
| const int32_t* offsets, | |
| const GLsizei* primcounts, | |
| const GLint* basevertices, | |
| const GLuint* baseinstances, | |
| GLsizei drawcount, | |
| Buffer* element_array_buffer, | |
| GLuint* total_max_vertex_accessed, | |
| GLsizei* total_max_primcount); | |
| bool CheckTransformFeedback(const char* function_name, | |
| bool instanced, | |
| GLenum mode, | |
| const GLsizei* counts, | |
| const GLsizei* primcounts, | |
| GLsizei drawcount, | |
| GLsizei* transform_feedback_vertices); | |
| // Handle MultiDrawArrays and MultiDrawElements for both instanced and | |
| // non-instanced cases (primcount is always 1 for non-instanced). | |
| // (basevertex and baseinstance are always 0 for non-basevertex-baseinstance | |
| // draws) | |
| template <DrawArraysOption option> | |
| error::Error DoMultiDrawArrays(const char* function_name, | |
| bool instanced, | |
| GLenum mode, | |
| const GLint* firsts, | |
| const GLsizei* counts, | |
| const GLsizei* primcounts, | |
| const GLuint* baseinstances, | |
| GLsizei drawcount); | |
| template <DrawElementsOption option> | |
| error::Error DoMultiDrawElements(const char* function_name, | |
| bool instanced, | |
| GLenum mode, | |
| const GLsizei* counts, | |
| GLenum type, | |
| const int32_t* offsets, | |
| const GLsizei* primcounts, | |
| const GLint* basevertices, | |
| const GLuint* baseinstances, | |
| GLsizei drawcount); | |
| GLenum GetBindTargetForSamplerType(GLenum type) { | |
| switch (type) { | |
| case GL_SAMPLER_2D: | |
| case GL_SAMPLER_2D_SHADOW: | |
| case GL_INT_SAMPLER_2D: | |
| case GL_UNSIGNED_INT_SAMPLER_2D: | |
| return GL_TEXTURE_2D; | |
| case GL_SAMPLER_CUBE: | |
| case GL_SAMPLER_CUBE_SHADOW: | |
| case GL_INT_SAMPLER_CUBE: | |
| case GL_UNSIGNED_INT_SAMPLER_CUBE: | |
| return GL_TEXTURE_CUBE_MAP; | |
| case GL_SAMPLER_EXTERNAL_OES: | |
| case GL_SAMPLER_EXTERNAL_2D_Y2Y_EXT: | |
| return GL_TEXTURE_EXTERNAL_OES; | |
| case GL_SAMPLER_2D_RECT_ANGLE: | |
| return GL_TEXTURE_RECTANGLE_ANGLE; | |
| case GL_SAMPLER_3D: | |
| case GL_INT_SAMPLER_3D: | |
| case GL_UNSIGNED_INT_SAMPLER_3D: | |
| return GL_TEXTURE_3D; | |
| case GL_SAMPLER_2D_ARRAY: | |
| case GL_SAMPLER_2D_ARRAY_SHADOW: | |
| case GL_INT_SAMPLER_2D_ARRAY: | |
| case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: | |
| return GL_TEXTURE_2D_ARRAY; | |
| default: | |
| NOTREACHED(); | |
| } | |
| } | |
| // Gets the framebuffer info for a particular target. | |
| Framebuffer* GetFramebufferInfoForTarget(GLenum target) const { | |
| Framebuffer* framebuffer = nullptr; | |
| switch (target) { | |
| case GL_FRAMEBUFFER: | |
| case GL_DRAW_FRAMEBUFFER: | |
| framebuffer = framebuffer_state_.bound_draw_framebuffer.get(); | |
| break; | |
| case GL_READ_FRAMEBUFFER: | |
| framebuffer = framebuffer_state_.bound_read_framebuffer.get(); | |
| break; | |
| default: | |
| NOTREACHED(); | |
| } | |
| return framebuffer; | |
| } | |
| Renderbuffer* GetRenderbufferInfoForTarget( | |
| GLenum target) { | |
| Renderbuffer* renderbuffer = nullptr; | |
| switch (target) { | |
| case GL_RENDERBUFFER: | |
| renderbuffer = state_.bound_renderbuffer.get(); | |
| break; | |
| default: | |
| NOTREACHED(); | |
| } | |
| return renderbuffer; | |
| } | |
| // Validates the program and location for a glGetUniform call and returns | |
| // a SizeResult setup to receive the result. Returns true if glGetUniform | |
| // should be called. | |
| template <class T> | |
| bool GetUniformSetup(GLuint program, | |
| GLint fake_location, | |
| uint32_t shm_id, | |
| uint32_t shm_offset, | |
| error::Error* error, | |
| GLint* real_location, | |
| GLuint* service_id, | |
| SizedResult<T>** result, | |
| GLenum* result_type, | |
| GLsizei* result_size); | |
| bool WasContextLost() const override; | |
| bool WasContextLostByRobustnessExtension() const override; | |
| void MarkContextLost(error::ContextLostReason reason) override; | |
| bool CheckResetStatus() override; | |
| bool ValidateCompressedTexDimensions( | |
| const char* function_name, GLenum target, GLint level, | |
| GLsizei width, GLsizei height, GLsizei depth, GLenum format); | |
| bool ValidateCompressedTexFuncData(const char* function_name, | |
| GLsizei width, | |
| GLsizei height, | |
| GLsizei depth, | |
| GLenum format, | |
| GLsizei size, | |
| const GLvoid* data); | |
| bool ValidateCompressedTexSubDimensions( | |
| const char* function_name, | |
| GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, | |
| GLsizei width, GLsizei height, GLsizei depth, GLenum format, | |
| Texture* texture); | |
| bool ValidateCopyTextureCHROMIUMTextures(const char* function_name, | |
| GLenum dest_target, | |
| TextureRef* source_texture_ref, | |
| TextureRef* dest_texture_ref); | |
| bool CanUseCopyTextureCHROMIUMInternalFormat(GLenum dest_internal_format); | |
| void CopySubTextureHelper(const char* function_name, | |
| GLuint source_id, | |
| GLint source_level, | |
| GLenum dest_target, | |
| GLuint dest_id, | |
| GLint dest_level, | |
| GLint xoffset, | |
| GLint yoffset, | |
| GLint x, | |
| GLint y, | |
| GLsizei width, | |
| GLsizei height, | |
| GLboolean unpack_flip_y, | |
| GLboolean unpack_premultiply_alpha, | |
| GLboolean unpack_unmultiply_alpha); | |
| void RenderWarning(const char* filename, int line, const std::string& msg); | |
| void PerformanceWarning( | |
| const char* filename, int line, const std::string& msg); | |
| const FeatureInfo::FeatureFlags& features() const { | |
| return feature_info_->feature_flags(); | |
| } | |
| const GpuDriverBugWorkarounds& workarounds() const { | |
| return feature_info_->workarounds(); | |
| } | |
| // Whether the back buffer exposed to the client has an alpha channel. Note | |
| // that this is potentially different from whether the implementation of the | |
| // back buffer has an alpha channel. | |
| bool ClientExposedBackBufferHasAlpha() const { | |
| if (back_buffer_draw_buffer_ == GL_NONE) | |
| return false; | |
| if (offscreen_target_frame_buffer_.get()) { | |
| return false; | |
| } | |
| return (back_buffer_color_format_ == GL_RGBA || | |
| back_buffer_color_format_ == GL_RGBA8); | |
| } | |
| // Set remaining commands to process to 0 to force DoCommands to return | |
| // and allow context preemption and GPU watchdog checks in CommandExecutor(). | |
| void ExitCommandProcessingEarly() override; | |
| void ProcessPendingReadPixels(bool did_finish); | |
| void FinishReadPixels(GLsizei width, | |
| GLsizei height, | |
| GLsizei format, | |
| GLsizei type, | |
| uint32_t pixels_shm_id, | |
| uint32_t pixels_shm_offset, | |
| uint32_t result_shm_id, | |
| uint32_t result_shm_offset, | |
| GLint pack_alignment, | |
| GLenum read_format, | |
| GLuint buffer); | |
| // Checks to see if the inserted fence has completed. | |
| void ProcessDescheduleUntilFinished(); | |
| // If |texture_manager_version_| doesn't match the current version, then this | |
| // will rebind all external textures to match their current service_id. | |
| void RestoreAllExternalTextureBindingsIfNeeded() override; | |
| const SamplerState& GetSamplerStateForTextureUnit(GLenum target, GLuint unit); | |
| // Helper method to call glClear workaround. | |
| // Clears the framebuffer via a blit on drivers where glClear is broken. | |
| // Returns false if the blit failed, in which case the targeted attachments | |
| // must not be recorded as cleared. | |
| bool ClearFramebufferForWorkaround(GLbitfield mask); | |
| bool SupportsSeparateFramebufferBinds() const { | |
| return (feature_info_->feature_flags().chromium_framebuffer_multisample || | |
| feature_info_->IsWebGL2OrES3Context()); | |
| } | |
| GLenum GetDrawFramebufferTarget() const { | |
| return SupportsSeparateFramebufferBinds() ? | |
| GL_DRAW_FRAMEBUFFER : GL_FRAMEBUFFER; | |
| } | |
| GLenum GetReadFramebufferTarget() const { | |
| return SupportsSeparateFramebufferBinds() ? | |
| GL_READ_FRAMEBUFFER : GL_FRAMEBUFFER; | |
| } | |
| Framebuffer* GetBoundDrawFramebuffer() const { | |
| return framebuffer_state_.bound_draw_framebuffer.get(); | |
| } | |
| Framebuffer* GetBoundReadFramebuffer() const { | |
| GLenum target = GetReadFramebufferTarget(); | |
| return GetFramebufferInfoForTarget(target); | |
| } | |
| bool InitializeCopyTexImageBlitter(const char* function_name); | |
| bool InitializeCopyTextureCHROMIUM(const char* function_name); | |
| void UnbindTexture(TextureRef* texture_ref, | |
| bool supports_separate_framebuffer_binds); | |
| void ReadBackBuffersIntoShadowCopies( | |
| base::flat_set<scoped_refptr<Buffer>> buffers_to_shadow_copy); | |
| // Compiles the given shader and exits command processing early. | |
| void CompileShaderAndExitCommandProcessingEarly(Shader* shader); | |
| // Notify the watchdog thread of progress, preventing time-outs when a | |
| // command takes a long time. May be no-op when using in-process command | |
| // buffer. | |
| void ReportProgress(); | |
| // Generate a member function prototype for each command in an automated and | |
| // typesafe way. | |
| #define GLES2_CMD_OP(name) \ | |
| Error Handle##name(uint32_t immediate_data_size, const volatile void* data); | |
| GLES2_COMMAND_LIST(GLES2_CMD_OP) | |
| #undef GLES2_CMD_OP | |
| // The GL context this decoder renders to on behalf of the client. | |
| scoped_refptr<gl::GLSurface> surface_; | |
| scoped_refptr<gl::GLContext> context_; | |
| // The ContextGroup for this decoder uses to track resources. | |
| scoped_refptr<ContextGroup> group_; | |
| DebugMarkerManager debug_marker_manager_; | |
| Logger logger_; | |
| std::unique_ptr<ErrorState> error_state_; | |
| // All the state for this context. | |
| ContextState state_; | |
| std::unique_ptr<TransformFeedbackManager> transform_feedback_manager_; | |
| // Current width and height of the offscreen frame buffer. | |
| gfx::Size offscreen_size_; | |
| // Util to help with GL. | |
| GLES2Util util_; | |
| // The buffer we bind to attrib 0 since OpenGL requires it (ES does not). | |
| GLuint attrib_0_buffer_id_; | |
| // The value currently in attrib_0. | |
| Vec4 attrib_0_value_; | |
| // Whether or not the attrib_0 buffer holds the attrib_0_value. | |
| bool attrib_0_buffer_matches_value_; | |
| // The size of attrib 0. | |
| GLsizei attrib_0_size_; | |
| // The buffer used to simulate GL_FIXED attribs. | |
| GLuint fixed_attrib_buffer_id_; | |
| // The size of fiixed attrib buffer. | |
| GLsizei fixed_attrib_buffer_size_; | |
| // The offscreen frame buffer that the client renders to. | |
| std::unique_ptr<BackFramebuffer> offscreen_target_frame_buffer_; | |
| std::unique_ptr<BackTexture> offscreen_target_color_texture_; | |
| // The format of the texture or renderbuffer backing the offscreen | |
| // framebuffer. | |
| GLenum offscreen_target_color_format_; | |
| GLint max_offscreen_framebuffer_size_; | |
| std::unique_ptr<FramebufferManager> framebuffer_manager_; | |
| std::unique_ptr<GLES2QueryManager> query_manager_; | |
| std::unique_ptr<GpuFenceManager> gpu_fence_manager_; | |
| std::unique_ptr<MultiDrawManager> multi_draw_manager_; | |
| std::unique_ptr<VertexArrayManager> vertex_array_manager_; | |
| base::flat_set<scoped_refptr<Buffer>> writes_submitted_but_not_completed_; | |
| // The format of the back buffer_ | |
| GLenum back_buffer_color_format_; | |
| bool back_buffer_has_depth_; | |
| bool back_buffer_has_stencil_; | |
| // Tracks read buffer and draw buffer for backbuffer, whether it's onscreen | |
| // or offscreen. | |
| // TODO(zmo): when ES3 APIs are exposed to Nacl, make sure read_buffer_ | |
| // setting is set correctly when SwapBuffers(). | |
| GLenum back_buffer_read_buffer_; | |
| GLenum back_buffer_draw_buffer_; | |
| bool surfaceless_; | |
| // Backbuffer attachments that are currently undefined. | |
| uint32_t backbuffer_needs_clear_bits_; | |
| // An always-complete FBO to use for workarounds | |
| GLuint complete_fbo_ = 0; | |
| GLuint complete_fbo_color_texture_ = 0; | |
| // The current decoder error communicates the decoder error through command | |
| // processing functions that do not return the error value. Should be set only | |
| // if not returning an error. | |
| error::Error current_decoder_error_; | |
| scoped_refptr<ShaderTranslatorInterface> vertex_translator_; | |
| scoped_refptr<ShaderTranslatorInterface> fragment_translator_; | |
| // Cached from ContextGroup | |
| raw_ptr<const Validators, DanglingUntriaged> validators_; | |
| scoped_refptr<FeatureInfo> feature_info_; | |
| int frame_number_; | |
| // Number of commands remaining to be processed in DoCommands(). | |
| int commands_to_process_; | |
| bool context_was_lost_; | |
| bool reset_by_robustness_extension_; | |
| bool supports_async_swap_; | |
| // These flags are used to override the state of the shared feature_info_ | |
| // member. Because the same FeatureInfo instance may be shared among many | |
| // contexts, the assumptions on the availablity of extensions in WebGL | |
| // contexts may be broken. These flags override the shared state to preserve | |
| // WebGL semantics. | |
| bool derivatives_explicitly_enabled_; | |
| bool fbo_render_mipmap_explicitly_enabled_; | |
| bool frag_depth_explicitly_enabled_; | |
| bool draw_buffers_explicitly_enabled_; | |
| bool shader_texture_lod_explicitly_enabled_; | |
| bool multi_draw_explicitly_enabled_; | |
| bool draw_instanced_base_vertex_base_instance_explicitly_enabled_; | |
| bool multi_draw_instanced_base_vertex_base_instance_explicitly_enabled_; | |
| bool arb_texture_rectangle_enabled_; | |
| bool oes_egl_image_external_enabled_; | |
| bool nv_egl_stream_consumer_external_enabled_; | |
| bool compile_shader_always_succeeds_; | |
| // An optional behaviour to lose the context and group when OOM. | |
| bool lose_context_when_out_of_memory_; | |
| // Log extra info. | |
| bool service_logging_; | |
| std::unique_ptr<CopyTexImageResourceManager> copy_tex_image_blit_; | |
| std::unique_ptr<CopyTextureCHROMIUMResourceManager> copy_texture_chromium_; | |
| std::unique_ptr<ClearFramebufferResourceManager> clear_framebuffer_blit_; | |
| // Cached values of the currently assigned viewport dimensions. | |
| GLsizei viewport_max_width_; | |
| GLsizei viewport_max_height_; | |
| // Cached value for the number of stencil bits for the default framebuffer. | |
| GLint num_stencil_bits_; | |
| // States related to each manager. | |
| DecoderTextureState texture_state_; | |
| DecoderFramebufferState framebuffer_state_; | |
| std::unique_ptr<GPUTracer> gpu_tracer_; | |
| std::unique_ptr<GPUStateTracer> gpu_state_tracer_; | |
| int gpu_trace_level_; | |
| bool gpu_trace_commands_; | |
| bool gpu_debug_commands_; | |
| base::queue<FenceCallback> pending_readpixel_fences_; | |
| // After a second fence is inserted, both the GpuChannelMessageQueue and | |
| // CommandExecutor are descheduled. Once the first fence has completed, both | |
| // get rescheduled. | |
| std::vector<std::unique_ptr<gl::GLFence>> deschedule_until_finished_fences_; | |
| // Used to validate multisample renderbuffers if needed | |
| typedef std::unordered_map<GLenum, GLuint> TextureMap; | |
| TextureMap validation_textures_; | |
| GLuint validation_fbo_multisample_; | |
| GLuint validation_fbo_; | |
| typedef gpu::gles2::GLES2Decoder::Error (GLES2DecoderImpl::*CmdHandler)( | |
| uint32_t immediate_data_size, | |
| const volatile void* data); | |
| // A struct to hold info about each command. | |
| struct CommandInfo { | |
| CmdHandler cmd_handler; | |
| uint8_t arg_flags; // How to handle the arguments for this command | |
| uint8_t cmd_flags; // How to handle this command | |
| uint16_t arg_count; // How many arguments are expected for this command. | |
| }; | |
| // A table of CommandInfo for all the commands. | |
| static const std::array<CommandInfo, kNumCommands - kFirstGLES2Command> | |
| command_info; | |
| // Most recent generation of the TextureManager. If this no longer matches | |
| // the current generation when our context becomes current, then we'll rebind | |
| // all the textures to stay up to date with Texture::service_id() changes. | |
| uint32_t texture_manager_service_id_generation_; | |
| bool force_shader_name_hashing_for_test = false; | |
| GLfloat line_width_range_[2] = {0.0, 1.0}; | |
| SamplerState default_sampler_state_; | |
| base::WeakPtrFactory<GLES2DecoderImpl> weak_ptr_factory_{this}; | |
| }; | |
| constexpr std::array<GLES2DecoderImpl::CommandInfo, | |
| kNumCommands - kFirstGLES2Command> | |
| GLES2DecoderImpl::command_info = {{ | |
| #define GLES2_CMD_OP(name) \ | |
| { \ | |
| &GLES2DecoderImpl::Handle##name, cmds::name::kArgFlags, \ | |
| cmds::name::cmd_flags, \ | |
| sizeof(cmds::name) / sizeof(CommandBufferEntry) - 1, \ | |
| } \ | |
| , /* NOLINT */ | |
| GLES2_COMMAND_LIST(GLES2_CMD_OP) | |
| #undef GLES2_CMD_OP | |
| }}; | |
| ScopedGLErrorSuppressor::ScopedGLErrorSuppressor( | |
| const char* function_name, ErrorState* error_state) | |
| : function_name_(function_name), | |
| error_state_(error_state) { | |
| ERRORSTATE_COPY_REAL_GL_ERRORS_TO_WRAPPER(error_state_, function_name_); | |
| } | |
| ScopedGLErrorSuppressor::~ScopedGLErrorSuppressor() { | |
| ERRORSTATE_CLEAR_REAL_GL_ERRORS(error_state_, function_name_); | |
| } | |
| class ScopedBufferReattacher { | |
| public: | |
| ScopedBufferReattacher(GLES2DecoderImpl* decoder, TextureRef* texture_ref); | |
| ScopedBufferReattacher(GLES2DecoderImpl* decoder, Renderbuffer* renderbuffer); | |
| ~ScopedBufferReattacher(); | |
| private: | |
| struct SavedAttachmentInfo { | |
| scoped_refptr<Framebuffer> framebuffer; | |
| GLenum attachment_point; | |
| GLenum texture_target = 0; | |
| GLint texture_level = 0; | |
| GLsizei texture_samples = 0; | |
| GLint texture_layer = 0; | |
| bool is_texture = false; | |
| bool is_renderbuffer = false; | |
| }; | |
| void Initialize(); | |
| void RestoreBindings(); | |
| raw_ptr<GLES2DecoderImpl> decoder_; | |
| raw_ptr<TextureRef> texture_ref_ = nullptr; | |
| raw_ptr<Renderbuffer> renderbuffer_ = nullptr; | |
| std::vector<SavedAttachmentInfo> saved_attachments_; | |
| scoped_refptr<Framebuffer> old_read_fbo_; | |
| scoped_refptr<Framebuffer> old_draw_fbo_; | |
| }; | |
| ScopedBufferReattacher::ScopedBufferReattacher(GLES2DecoderImpl* decoder, | |
| TextureRef* texture_ref) | |
| : decoder_(decoder), texture_ref_(texture_ref) { | |
| Initialize(); | |
| } | |
| ScopedBufferReattacher::ScopedBufferReattacher(GLES2DecoderImpl* decoder, | |
| Renderbuffer* renderbuffer) | |
| : decoder_(decoder), renderbuffer_(renderbuffer) { | |
| Initialize(); | |
| } | |
| bool IsLayeredTextureTarget(GLenum texture_target) { | |
| switch (texture_target) { | |
| case GL_TEXTURE_2D_ARRAY: | |
| case GL_TEXTURE_2D_MULTISAMPLE_ARRAY: | |
| case GL_TEXTURE_3D: | |
| case GL_TEXTURE_CUBE_MAP: | |
| case GL_TEXTURE_CUBE_MAP_ARRAY: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| void ScopedBufferReattacher::Initialize() { | |
| const bool reattach_depth_stencil = | |
| decoder_->workarounds().reattach_fbo_depth_stencil_on_reallocation; | |
| const bool reattach_layer_increase = | |
| decoder_->workarounds().reattach_texture_to_fbo_after_layer_increase && | |
| texture_ref_ && IsLayeredTextureTarget(texture_ref_->texture()->target()); | |
| if (!reattach_depth_stencil && !reattach_layer_increase) { | |
| return; | |
| } | |
| std::vector<std::pair<scoped_refptr<Framebuffer>, GLenum>> detached_fbos; | |
| if (texture_ref_) { | |
| detached_fbos = | |
| decoder_->framebuffer_manager()->GetBindingFramebuffersForTexture( | |
| texture_ref_, reattach_layer_increase); | |
| } else if (renderbuffer_) { | |
| detached_fbos = | |
| decoder_->framebuffer_manager()->GetBindingFramebuffersForRenderbuffer( | |
| renderbuffer_); | |
| } | |
| if (detached_fbos.empty()) { | |
| return; | |
| } | |
| // Save old bindings to restore them later. | |
| old_read_fbo_ = decoder_->framebuffer_state_.bound_read_framebuffer; | |
| old_draw_fbo_ = decoder_->framebuffer_state_.bound_draw_framebuffer; | |
| // Save attachment details and detach. | |
| for (const auto& pair : detached_fbos) { | |
| Framebuffer* fbo = pair.first.get(); | |
| GLenum attachment_point = pair.second; | |
| const Framebuffer::Attachment* attachment = | |
| fbo->GetAttachment(attachment_point); | |
| if (!attachment) { | |
| continue; | |
| } | |
| SavedAttachmentInfo info; | |
| info.framebuffer = pair.first; | |
| info.attachment_point = attachment_point; | |
| if (attachment->IsTextureAttachment()) { | |
| info.is_texture = true; | |
| info.texture_target = attachment->target(); | |
| info.texture_level = attachment->level(); | |
| info.texture_samples = attachment->samples(); | |
| info.texture_layer = attachment->layer(); | |
| } else if (attachment->IsRenderbufferAttachment()) { | |
| info.is_renderbuffer = true; | |
| } | |
| saved_attachments_.push_back(info); | |
| // Detach in driver. | |
| decoder_->api()->glBindFramebufferEXTFn(GL_FRAMEBUFFER, fbo->service_id()); | |
| if (info.is_texture) { | |
| if (info.texture_target == GL_TEXTURE_2D_ARRAY || | |
| info.texture_target == GL_TEXTURE_3D) { | |
| decoder_->api()->glFramebufferTextureLayerFn(GL_FRAMEBUFFER, | |
| attachment_point, 0, 0, 0); | |
| } else { | |
| decoder_->api()->glFramebufferTexture2DEXTFn( | |
| GL_FRAMEBUFFER, attachment_point, info.texture_target, 0, 0); | |
| } | |
| } else if (info.is_renderbuffer) { | |
| decoder_->api()->glFramebufferRenderbufferEXTFn( | |
| GL_FRAMEBUFFER, attachment_point, GL_RENDERBUFFER, 0); | |
| } | |
| } | |
| if (!saved_attachments_.empty()) { | |
| RestoreBindings(); | |
| } | |
| } | |
| ScopedBufferReattacher::~ScopedBufferReattacher() { | |
| if (saved_attachments_.empty()) { | |
| return; | |
| } | |
| // Reattach. | |
| for (const auto& info : saved_attachments_) { | |
| Framebuffer* fbo = info.framebuffer.get(); | |
| decoder_->api()->glBindFramebufferEXTFn(GL_FRAMEBUFFER, fbo->service_id()); | |
| if (info.is_texture) { | |
| if (info.texture_target != GL_TEXTURE_2D_ARRAY && | |
| info.texture_target != GL_TEXTURE_3D) { | |
| if (info.texture_samples == 0) { | |
| decoder_->api()->glFramebufferTexture2DEXTFn( | |
| GL_FRAMEBUFFER, info.attachment_point, info.texture_target, | |
| texture_ref_->service_id(), info.texture_level); | |
| } else { | |
| decoder_->api()->glFramebufferTexture2DMultisampleEXTFn( | |
| GL_FRAMEBUFFER, info.attachment_point, info.texture_target, | |
| texture_ref_->service_id(), info.texture_level, | |
| info.texture_samples); | |
| } | |
| } else { | |
| decoder_->api()->glFramebufferTextureLayerFn( | |
| GL_FRAMEBUFFER, info.attachment_point, texture_ref_->service_id(), | |
| info.texture_level, info.texture_layer); | |
| } | |
| } else if (info.is_renderbuffer) { | |
| decoder_->api()->glFramebufferRenderbufferEXTFn( | |
| GL_FRAMEBUFFER, info.attachment_point, GL_RENDERBUFFER, | |
| renderbuffer_->service_id()); | |
| } | |
| } | |
| RestoreBindings(); | |
| } | |
| void ScopedBufferReattacher::RestoreBindings() { | |
| // Restore bindings. A null tracked framebuffer means the client has the | |
| // default framebuffer bound, which on this decoder is the emulated | |
| // backbuffer, not driver FBO 0 (see RestoreCurrentFramebufferBindings and | |
| // RestoreFramebufferBindings). Restoring raw FBO 0 here desyncs the driver | |
| // binding from the tracked state and lets the pending backbuffer clear | |
| // (backbuffer_needs_clear_bits_) be consumed against the wrong framebuffer. | |
| if (old_read_fbo_ == old_draw_fbo_) { | |
| GLuint service_id = old_read_fbo_ ? old_read_fbo_->service_id() | |
| : decoder_->GetBackbufferServiceId(); | |
| decoder_->api()->glBindFramebufferEXTFn(GL_FRAMEBUFFER, service_id); | |
| } else { | |
| GLuint read_id = old_read_fbo_ ? old_read_fbo_->service_id() | |
| : decoder_->GetBackbufferServiceId(); | |
| GLuint draw_id = old_draw_fbo_ ? old_draw_fbo_->service_id() | |
| : decoder_->GetBackbufferServiceId(); | |
| decoder_->api()->glBindFramebufferEXTFn(GL_READ_FRAMEBUFFER, read_id); | |
| decoder_->api()->glBindFramebufferEXTFn(GL_DRAW_FRAMEBUFFER, draw_id); | |
| } | |
| } | |
| static void RestoreCurrentTextureBindings(ContextState* state, | |
| GLenum target, | |
| GLuint texture_unit) { | |
| DCHECK(!state->texture_units.empty()); | |
| DCHECK_LT(texture_unit, state->texture_units.size()); | |
| TextureUnit& info = state->texture_units[texture_unit]; | |
| GLuint last_id; | |
| TextureRef* texture_ref = info.GetInfoForTarget(target); | |
| if (texture_ref) { | |
| last_id = texture_ref->service_id(); | |
| } else { | |
| last_id = 0; | |
| } | |
| state->api()->glBindTextureFn(target, last_id); | |
| } | |
| ScopedTextureBinder::ScopedTextureBinder(ContextState* state, | |
| ErrorState* error_state, | |
| GLuint id, | |
| GLenum target) | |
| : state_(state), error_state_(error_state), target_(target) { | |
| ScopedGLErrorSuppressor suppressor("ScopedTextureBinder::ctor", error_state_); | |
| // TODO(apatrick): Check if there are any other states that need to be reset | |
| // before binding a new texture. | |
| auto* api = state->api(); | |
| api->glActiveTextureFn(GL_TEXTURE0); | |
| api->glBindTextureFn(target, id); | |
| } | |
| ScopedTextureBinder::~ScopedTextureBinder() { | |
| ScopedGLErrorSuppressor suppressor("ScopedTextureBinder::dtor", error_state_); | |
| RestoreCurrentTextureBindings(state_, target_, 0); | |
| state_->RestoreActiveTexture(); | |
| } | |
| ScopedFramebufferBinder::ScopedFramebufferBinder(GLES2DecoderImpl* decoder, | |
| GLuint id) | |
| : decoder_(decoder) { | |
| ScopedGLErrorSuppressor suppressor("ScopedFramebufferBinder::ctor", | |
| decoder_->error_state_.get()); | |
| decoder->BindFramebuffer(GL_FRAMEBUFFER, id); | |
| decoder->OnFboChanged(); | |
| } | |
| ScopedFramebufferBinder::~ScopedFramebufferBinder() { | |
| ScopedGLErrorSuppressor suppressor("ScopedFramebufferBinder::dtor", | |
| decoder_->error_state_.get()); | |
| decoder_->RestoreCurrentFramebufferBindings(); | |
| } | |
| ScopedFramebufferCopyBinder::ScopedFramebufferCopyBinder( | |
| GLES2DecoderImpl* decoder, | |
| GLint x, | |
| GLint y, | |
| GLint width, | |
| GLint height) | |
| : decoder_(decoder) { | |
| const Framebuffer::Attachment* attachment = | |
| decoder->framebuffer_state_.bound_read_framebuffer.get() | |
| ->GetReadBufferAttachment(); | |
| DCHECK(attachment); | |
| auto* api = decoder_->api(); | |
| api->glGenTexturesFn(1, &temp_texture_); | |
| ScopedTextureBinder texture_binder(&decoder->state_, | |
| decoder->error_state_.get(), temp_texture_, | |
| GL_TEXTURE_2D); | |
| if (width == 0 || height == 0) { | |
| // Copy the whole framebuffer if a rectangle isn't specified. | |
| api->glCopyTexImage2DFn(GL_TEXTURE_2D, 0, attachment->internal_format(), 0, | |
| 0, attachment->width(), attachment->height(), 0); | |
| } else { | |
| api->glCopyTexImage2DFn(GL_TEXTURE_2D, 0, attachment->internal_format(), x, | |
| y, width, height, 0); | |
| } | |
| api->glGenFramebuffersEXTFn(1, &temp_framebuffer_); | |
| framebuffer_binder_ = | |
| std::make_unique<ScopedFramebufferBinder>(decoder, temp_framebuffer_); | |
| api->glFramebufferTexture2DEXTFn(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, | |
| GL_TEXTURE_2D, temp_texture_, 0); | |
| api->glReadBufferFn(GL_COLOR_ATTACHMENT0); | |
| } | |
| ScopedFramebufferCopyBinder::~ScopedFramebufferCopyBinder() { | |
| auto* api = decoder_->api(); | |
| framebuffer_binder_.reset(); | |
| api->glDeleteFramebuffersEXTFn(1, &temp_framebuffer_); | |
| api->glDeleteTexturesFn(1, &temp_texture_); | |
| api->glReadBufferFn( | |
| decoder_->framebuffer_state_.bound_read_framebuffer.get()->read_buffer()); | |
| } | |
| ScopedPixelUnpackState::ScopedPixelUnpackState(ContextState* state) | |
| : state_(state) { | |
| DCHECK(state_); | |
| state_->PushTextureUnpackState(); | |
| } | |
| ScopedPixelUnpackState::~ScopedPixelUnpackState() { | |
| state_->RestoreUnpackState(); | |
| } | |
| ScopedCompressedUnpackStateScrub::ScopedCompressedUnpackStateScrub( | |
| ContextState* state) | |
| : state_(state) { | |
| DCHECK(state_); | |
| // These are ES3 pixel store parameters, forwarded to the real driver only | |
| // while a pixel unpack buffer is bound; without one the driver already | |
| // holds their initial values. Deliberately wider than the minimum: like | |
| // ContextState::PushTextureUnpackState() (and ANGLE's compressed entry | |
| // points, which always use an empty PixelUnpackState), the scrub runs | |
| // whenever a pixel unpack buffer is bound, even if the tracked values are | |
| // already the defaults, so the guarantee does not depend on the tracked | |
| // state mirroring the driver state. | |
| active_ = state_->bound_pixel_unpack_buffer.get() != nullptr; | |
| if (active_) { | |
| state_->SetUnpackParametersForCompressedTexImage(); | |
| } | |
| } | |
| ScopedCompressedUnpackStateScrub::~ScopedCompressedUnpackStateScrub() { | |
| // Re-applies the tracked unpack geometry (the client's values while a pixel | |
| // unpack buffer is bound) — the same logic used everywhere else the service | |
| // restores this state. | |
| if (active_) { | |
| state_->UpdateUnpackParameters(); | |
| } | |
| } | |
| BackTexture::BackTexture(GLES2DecoderImpl* decoder) | |
| : memory_tracker_(decoder->memory_tracker()), | |
| bytes_allocated_(0), | |
| decoder_(decoder) {} | |
| BackTexture::~BackTexture() { | |
| // This does not destroy the render texture because that would require that | |
| // the associated GL context was current. Just check that it was explicitly | |
| // destroyed. | |
| DCHECK_EQ(id(), 0u); | |
| } | |
| inline gl::GLApi* BackTexture::api() const { | |
| return decoder_->api(); | |
| } | |
| void BackTexture::Create() { | |
| DCHECK_EQ(id(), 0u); | |
| ScopedGLErrorSuppressor suppressor("BackTexture::Create", | |
| decoder_->error_state_.get()); | |
| GLuint id; | |
| api()->glGenTexturesFn(1, &id); | |
| GLenum target = Target(); | |
| ScopedTextureBinder binder(&decoder_->state_, decoder_->error_state_.get(), | |
| id, target); | |
| // No client id is necessary because this texture will never be directly | |
| // accessed by a client, only indirectly via a mailbox. | |
| texture_ref_ = TextureRef::Create(decoder_->texture_manager(), 0, id); | |
| decoder_->texture_manager()->SetTarget(texture_ref_.get(), target); | |
| decoder_->texture_manager()->SetParameteri( | |
| "BackTexture::Create", decoder_->error_state_.get(), texture_ref_.get(), | |
| GL_TEXTURE_MAG_FILTER, GL_LINEAR); | |
| decoder_->texture_manager()->SetParameteri( | |
| "BackTexture::Create", decoder_->error_state_.get(), texture_ref_.get(), | |
| GL_TEXTURE_MIN_FILTER, GL_LINEAR); | |
| decoder_->texture_manager()->SetParameteri( | |
| "BackTexture::Create", decoder_->error_state_.get(), texture_ref_.get(), | |
| GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | |
| decoder_->texture_manager()->SetParameteri( | |
| "BackTexture::Create", decoder_->error_state_.get(), texture_ref_.get(), | |
| GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |
| } | |
| bool BackTexture::AllocateStorage( | |
| const gfx::Size& size, GLenum format, bool zero) { | |
| DCHECK_NE(id(), 0u); | |
| ScopedGLErrorSuppressor suppressor("BackTexture::AllocateStorage", | |
| decoder_->error_state_.get()); | |
| ScopedTextureBinder binder(&decoder_->state_, decoder_->error_state_.get(), | |
| id(), Target()); | |
| uint32_t image_size = 0; | |
| GLES2Util::ComputeImageDataSizes(size.width(), size.height(), 1, format, | |
| GL_UNSIGNED_BYTE, 8, &image_size, nullptr, | |
| nullptr); | |
| size_ = size; | |
| { | |
| // Add extra scope to destroy zero_data and the object it owns right | |
| // after its usage. | |
| base::HeapArray<char> zero_data; | |
| if (zero) { | |
| zero_data = base::HeapArray<char>::WithSize(image_size); | |
| } | |
| api()->glTexImage2DFn(Target(), | |
| 0, // mip level | |
| format, size.width(), size.height(), | |
| 0, // border | |
| format, GL_UNSIGNED_BYTE, zero_data.data()); | |
| } | |
| decoder_->texture_manager()->SetLevelInfo( | |
| texture_ref_.get(), Target(), | |
| 0, // level | |
| GL_RGBA, size.width(), size.height(), | |
| 1, // depth | |
| 0, // border | |
| GL_RGBA, GL_UNSIGNED_BYTE, gfx::Rect(size)); | |
| const bool success = api()->glGetErrorFn() == GL_NO_ERROR; | |
| if (success) { | |
| memory_tracker_.TrackMemFree(bytes_allocated_); | |
| bytes_allocated_ = image_size; | |
| memory_tracker_.TrackMemAlloc(bytes_allocated_); | |
| } | |
| return success; | |
| } | |
| void BackTexture::Copy() { | |
| DCHECK_NE(id(), 0u); | |
| ScopedGLErrorSuppressor suppressor("BackTexture::Copy", | |
| decoder_->error_state_.get()); | |
| ScopedTextureBinder binder(&decoder_->state_, decoder_->error_state_.get(), | |
| id(), Target()); | |
| api()->glCopyTexSubImage2DFn(Target(), | |
| 0, // level | |
| 0, 0, 0, 0, size_.width(), size_.height()); | |
| } | |
| void BackTexture::Destroy() { | |
| if (texture_ref_) { | |
| ScopedGLErrorSuppressor suppressor("BackTexture::Destroy", | |
| decoder_->error_state_.get()); | |
| texture_ref_ = nullptr; | |
| } | |
| memory_tracker_.TrackMemFree(bytes_allocated_); | |
| bytes_allocated_ = 0; | |
| } | |
| void BackTexture::Invalidate() { | |
| if (texture_ref_) { | |
| texture_ref_->ForceContextLost(); | |
| texture_ref_ = nullptr; | |
| } | |
| memory_tracker_.TrackMemFree(bytes_allocated_); | |
| bytes_allocated_ = 0; | |
| } | |
| GLenum BackTexture::Target() { | |
| return GL_TEXTURE_2D; | |
| } | |
| BackFramebuffer::BackFramebuffer(GLES2DecoderImpl* decoder) | |
| : decoder_(decoder), | |
| id_(0) { | |
| } | |
| BackFramebuffer::~BackFramebuffer() { | |
| // This does not destroy the frame buffer because that would require that | |
| // the associated GL context was current. Just check that it was explicitly | |
| // destroyed. | |
| DCHECK_EQ(id_, 0u); | |
| } | |
| inline gl::GLApi* BackFramebuffer::api() const { | |
| return decoder_->api(); | |
| } | |
| void BackFramebuffer::Create() { | |
| ScopedGLErrorSuppressor suppressor("BackFramebuffer::Create", | |
| decoder_->error_state_.get()); | |
| Destroy(); | |
| api()->glGenFramebuffersEXTFn(1, &id_); | |
| } | |
| void BackFramebuffer::AttachRenderTexture(BackTexture* texture) { | |
| DCHECK_NE(id_, 0u); | |
| ScopedGLErrorSuppressor suppressor("BackFramebuffer::AttachRenderTexture", | |
| decoder_->error_state_.get()); | |
| ScopedFramebufferBinder binder(decoder_, id_); | |
| GLuint attach_id = texture ? texture->id() : 0; | |
| api()->glFramebufferTexture2DEXTFn(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, | |
| texture->Target(), attach_id, 0); | |
| } | |
| void BackFramebuffer::Destroy() { | |
| if (id_ != 0) { | |
| ScopedGLErrorSuppressor suppressor("BackFramebuffer::Destroy", | |
| decoder_->error_state_.get()); | |
| api()->glDeleteFramebuffersEXTFn(1, &id_); | |
| id_ = 0; | |
| } | |
| } | |
| void BackFramebuffer::Invalidate() { | |
| id_ = 0; | |
| } | |
| GLenum BackFramebuffer::CheckStatus() { | |
| DCHECK_NE(id_, 0u); | |
| ScopedGLErrorSuppressor suppressor("BackFramebuffer::CheckStatus", | |
| decoder_->error_state_.get()); | |
| ScopedFramebufferBinder binder(decoder_, id_); | |
| return api()->glCheckFramebufferStatusEXTFn(GL_FRAMEBUFFER); | |
| } | |
| std::unique_ptr<GLES2Decoder> GLES2Decoder::Create( | |
| DecoderClient* client, | |
| CommandBufferServiceBase* command_buffer_service, | |
| Outputter* outputter, | |
| ContextGroup* group) { | |
| if (group->use_passthrough_cmd_decoder()) { | |
| return std::make_unique<GLES2DecoderPassthroughImpl>( | |
| client, command_buffer_service, outputter, group); | |
| } | |
| // Allow linux to run fuzzers. | |
| #if BUILDFLAG(ENABLE_VALIDATING_COMMAND_DECODER) || BUILDFLAG(IS_LINUX) | |
| return std::make_unique<GLES2DecoderImpl>(client, command_buffer_service, | |
| outputter, group); | |
| #else | |
| LOG(FATAL) << "Validating command decoder is not supported."; | |
| #endif | |
| } | |
| GLES2DecoderImpl::GLES2DecoderImpl( | |
| DecoderClient* client, | |
| CommandBufferServiceBase* command_buffer_service, | |
| Outputter* outputter, | |
| ContextGroup* group) | |
| : GLES2Decoder(client, command_buffer_service, outputter), | |
| group_(group), | |
| logger_(&debug_marker_manager_, | |
| base::BindRepeating(&DecoderClient::OnConsoleMessage, | |
| base::Unretained(client), | |
| 0), | |
| group->gpu_preferences().disable_gl_error_limit), | |
| error_state_(ErrorState::Create(this, &logger_)), | |
| state_(group_->feature_info()), | |
| attrib_0_buffer_id_(0), | |
| attrib_0_buffer_matches_value_(true), | |
| attrib_0_size_(0), | |
| fixed_attrib_buffer_id_(0), | |
| fixed_attrib_buffer_size_(0), | |
| offscreen_target_color_format_(0), | |
| max_offscreen_framebuffer_size_(0), | |
| back_buffer_color_format_(0), | |
| back_buffer_has_depth_(false), | |
| back_buffer_has_stencil_(false), | |
| back_buffer_read_buffer_(GL_BACK), | |
| back_buffer_draw_buffer_(GL_BACK), | |
| surfaceless_(false), | |
| backbuffer_needs_clear_bits_(0), | |
| current_decoder_error_(error::kNoError), | |
| validators_(group_->feature_info()->validators()), | |
| feature_info_(group_->feature_info()), | |
| frame_number_(0), | |
| context_was_lost_(false), | |
| reset_by_robustness_extension_(false), | |
| supports_async_swap_(false), | |
| derivatives_explicitly_enabled_(false), | |
| fbo_render_mipmap_explicitly_enabled_(false), | |
| frag_depth_explicitly_enabled_(false), | |
| draw_buffers_explicitly_enabled_(false), | |
| shader_texture_lod_explicitly_enabled_(false), | |
| multi_draw_explicitly_enabled_(false), | |
| draw_instanced_base_vertex_base_instance_explicitly_enabled_(false), | |
| multi_draw_instanced_base_vertex_base_instance_explicitly_enabled_(false), | |
| arb_texture_rectangle_enabled_(false), | |
| oes_egl_image_external_enabled_(false), | |
| nv_egl_stream_consumer_external_enabled_(false), | |
| compile_shader_always_succeeds_(false), | |
| lose_context_when_out_of_memory_(false), | |
| service_logging_( | |
| group_->gpu_preferences().enable_gpu_service_logging_gpu), | |
| viewport_max_width_(0), | |
| viewport_max_height_(0), | |
| num_stencil_bits_(0), | |
| texture_state_(group_->feature_info()->workarounds()), | |
| gpu_trace_level_(2), | |
| gpu_trace_commands_(false), | |
| gpu_debug_commands_(false), | |
| validation_fbo_multisample_(0), | |
| validation_fbo_(0), | |
| texture_manager_service_id_generation_(0) { | |
| DCHECK(client); | |
| DCHECK(group); | |
| } | |
| GLES2DecoderImpl::~GLES2DecoderImpl() = default; | |
| base::WeakPtr<DecoderContext> GLES2DecoderImpl::AsWeakPtr() { | |
| return weak_ptr_factory_.GetWeakPtr(); | |
| } | |
| gpu::ContextResult GLES2DecoderImpl::Initialize( | |
| const scoped_refptr<gl::GLSurface>& surface, | |
| const scoped_refptr<gl::GLContext>& context, | |
| bool offscreen, | |
| ContextType context_type, | |
| bool lose_context_when_out_of_memory) { | |
| TRACE_EVENT0("gpu", "GLES2DecoderImpl::Initialize"); | |
| DCHECK(context->IsCurrent(surface.get())); | |
| DCHECK(!context_.get()); | |
| state_.set_api(gl::g_current_gl_context); | |
| surfaceless_ = surface->IsSurfaceless() && !offscreen; | |
| set_initialized(); | |
| // At this point we are partially initialized and must Destroy() in any | |
| // failure case. | |
| DestroyOnFailure destroy_on_failure(this); | |
| gpu_state_tracer_ = GPUStateTracer::Create(&state_); | |
| if (group_->gpu_preferences().enable_gpu_debugging) | |
| set_debug(true); | |
| if (group_->gpu_preferences().enable_gpu_command_logging) | |
| SetLogCommands(true); | |
| compile_shader_always_succeeds_ = | |
| group_->gpu_preferences().compile_shader_always_succeeds; | |
| // Take ownership of the context and surface. The surface can be replaced with | |
| // SetSurface. | |
| context_ = context; | |
| surface_ = surface; | |
| // Create GPU Tracer for timing values. | |
| gpu_tracer_ = std::make_unique<GPUTracer>(this); | |
| if (workarounds().disable_timestamp_queries) { | |
| // Forcing time elapsed query for any GPU Timing Client forces it for all | |
| // clients in the context. | |
| GetGLContext()->CreateGPUTimingClient()->ForceTimeElapsedQuery(); | |
| } | |
| lose_context_when_out_of_memory_ = lose_context_when_out_of_memory; | |
| if (workarounds().ensure_previous_framebuffer_not_deleted) { | |
| // Use a 1x1 RGBA8 framebuffer as the "always complete" framebuffer to bind | |
| // before binding other framebuffers | |
| api()->glGenTexturesFn(1, &complete_fbo_color_texture_); | |
| api()->glBindTextureFn(GL_TEXTURE_2D, complete_fbo_color_texture_); | |
| api()->glTexImage2DFn(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, | |
| GL_UNSIGNED_BYTE, nullptr); | |
| api()->glGenFramebuffersEXTFn(1, &complete_fbo_); | |
| api()->glBindFramebufferEXTFn(GL_FRAMEBUFFER, complete_fbo_); | |
| api()->glFramebufferTexture2DEXTFn(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, | |
| GL_TEXTURE_2D, | |
| complete_fbo_color_texture_, 0); | |
| CHECK_EQ(api()->glCheckFramebufferStatusEXTFn(GL_FRAMEBUFFER), | |
| static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE)); | |
| } | |
| CHECK_GL_ERROR(); | |
| auto result = group_->InitializeWithCompleteFramebufferForWorkarounds( | |
| this, context_type, complete_fbo_); | |
| if (result != gpu::ContextResult::kSuccess) { | |
| // Must not destroy ContextGroup if it is not initialized. | |
| group_ = nullptr; | |
| return result; | |
| } | |
| CHECK_GL_ERROR(); | |
| // In theory |needs_emulation| needs to be true on Desktop GL 4.1 or lower. | |
| // However, we set it to true everywhere, not to trust drivers to handle | |
| // out-of-bounds buffer accesses. | |
| bool needs_emulation = true; | |
| transform_feedback_manager_ = std::make_unique<TransformFeedbackManager>( | |
| group_->max_transform_feedback_separate_attribs(), needs_emulation); | |
| if (feature_info_->IsWebGL2OrES3Context()) { | |
| // Verified in ContextGroup. | |
| DCHECK(feature_info_->IsES3Capable()); | |
| feature_info_->EnableES3Validators(); | |
| frag_depth_explicitly_enabled_ = true; | |
| draw_buffers_explicitly_enabled_ = true; | |
| // TODO(zmo): Look into shader_texture_lod_explicitly_enabled_ situation. | |
| // Create a fake default transform feedback and bind to it. | |
| GLuint default_transform_feedback = 0; | |
| api()->glGenTransformFeedbacksFn(1, &default_transform_feedback); | |
| state_.default_transform_feedback = | |
| transform_feedback_manager_->CreateTransformFeedback( | |
| 0, default_transform_feedback); | |
| api()->glBindTransformFeedbackFn(GL_TRANSFORM_FEEDBACK, | |
| default_transform_feedback); | |
| state_.bound_transform_feedback = state_.default_transform_feedback.get(); | |
| state_.bound_transform_feedback->SetIsBound(true); | |
| } | |
| state_.indexed_uniform_buffer_bindings = | |
| base::MakeRefCounted<gles2::IndexedBufferBindingHost>( | |
| group_->max_uniform_buffer_bindings(), GL_UNIFORM_BUFFER, | |
| needs_emulation, | |
| workarounds().round_down_uniform_bind_buffer_range_size); | |
| state_.indexed_uniform_buffer_bindings->SetIsBound(true); | |
| state_.InitGenericAttribs(group_->max_vertex_attribs()); | |
| vertex_array_manager_ = std::make_unique<VertexArrayManager>(); | |
| GLuint default_vertex_attrib_service_id = 0; | |
| if (features().native_vertex_array_object) { | |
| api()->glGenVertexArraysOESFn(1, &default_vertex_attrib_service_id); | |
| api()->glBindVertexArrayOESFn(default_vertex_attrib_service_id); | |
| } | |
| state_.default_vertex_attrib_manager = | |
| CreateVertexAttribManager(0, default_vertex_attrib_service_id, false); | |
| state_.default_vertex_attrib_manager->Initialize( | |
| group_->max_vertex_attribs()); | |
| // vertex_attrib_manager is set to default_vertex_attrib_manager by this call | |
| DoBindVertexArrayOES(0); | |
| framebuffer_manager_ = std::make_unique<FramebufferManager>( | |
| group_->max_draw_buffers(), group_->max_color_attachments(), | |
| group_->framebuffer_completeness_cache()); | |
| group_->texture_manager()->AddFramebufferManager(framebuffer_manager_.get()); | |
| query_manager_ = | |
| std::make_unique<GLES2QueryManager>(this, feature_info_.get()); | |
| gpu_fence_manager_ = std::make_unique<GpuFenceManager>(); | |
| multi_draw_manager_ = std::make_unique<MultiDrawManager>( | |
| MultiDrawManager::IndexStorageType::Offset); | |
| util_.set_num_compressed_texture_formats( | |
| validators_->compressed_texture_format.GetValues().size()); | |
| api()->glGenBuffersARBFn(1, &attrib_0_buffer_id_); | |
| api()->glBindBufferFn(GL_ARRAY_BUFFER, attrib_0_buffer_id_); | |
| api()->glVertexAttribPointerFn(0, 1, GL_FLOAT, GL_FALSE, 0, nullptr); | |
| api()->glBindBufferFn(GL_ARRAY_BUFFER, 0); | |
| api()->glGenBuffersARBFn(1, &fixed_attrib_buffer_id_); | |
| state_.texture_units.resize(group_->max_texture_units()); | |
| state_.sampler_units.resize(group_->max_texture_units()); | |
| for (uint32_t tt = 0; tt < state_.texture_units.size(); ++tt) { | |
| api()->glActiveTextureFn(GL_TEXTURE0 + tt); | |
| // We want the last bind to be 2D. | |
| TextureRef* ref; | |
| if (features().oes_egl_image_external || | |
| features().nv_egl_stream_consumer_external) { | |
| ref = texture_manager()->GetDefaultTextureInfo( | |
| GL_TEXTURE_EXTERNAL_OES); | |
| state_.texture_units[tt].bound_texture_external_oes = ref; | |
| api()->glBindTextureFn(GL_TEXTURE_EXTERNAL_OES, | |
| ref ? ref->service_id() : 0); | |
| } | |
| if (features().arb_texture_rectangle) { | |
| ref = | |
| texture_manager()->GetDefaultTextureInfo(GL_TEXTURE_RECTANGLE_ANGLE); | |
| state_.texture_units[tt].bound_texture_rectangle_arb = ref; | |
| api()->glBindTextureFn(GL_TEXTURE_RECTANGLE_ANGLE, | |
| ref ? ref->service_id() : 0); | |
| } | |
| ref = texture_manager()->GetDefaultTextureInfo(GL_TEXTURE_CUBE_MAP); | |
| state_.texture_units[tt].bound_texture_cube_map = ref; | |
| api()->glBindTextureFn(GL_TEXTURE_CUBE_MAP, ref ? ref->service_id() : 0); | |
| ref = texture_manager()->GetDefaultTextureInfo(GL_TEXTURE_2D); | |
| state_.texture_units[tt].bound_texture_2d = ref; | |
| api()->glBindTextureFn(GL_TEXTURE_2D, ref ? ref->service_id() : 0); | |
| } | |
| api()->glActiveTextureFn(GL_TEXTURE0); | |
| CHECK_GL_ERROR(); | |
| // cache ALPHA_BITS result for re-use with clear behaviour | |
| GLint alpha_bits = 0; | |
| if (offscreen) { | |
| offscreen_target_color_format_ = | |
| workarounds().disable_gl_rgb_format ? GL_RGBA : GL_RGB; | |
| max_offscreen_framebuffer_size_ = | |
| std::min(renderbuffer_manager()->max_renderbuffer_size(), | |
| texture_manager()->MaxSizeForTarget(GL_TEXTURE_2D)); | |
| // Use 64x64 instead of 1x1 to handle minimum framebuffer size | |
| // requirement on some platforms: b/265847440. | |
| gfx::Size initial_size = gfx::Size(64, 64); | |
| state_.viewport_width = initial_size.width(); | |
| state_.viewport_height = initial_size.height(); | |
| } else { | |
| BindFramebuffer(GL_FRAMEBUFFER, GetBackbufferServiceId()); | |
| // These are NOT if the back buffer has these proprorties. They are | |
| // if we want the command buffer to enforce them regardless of what | |
| // the real backbuffer is assuming the real back buffer gives us more than | |
| // we ask for. In other words, if we ask for RGB and we get RGBA then we'll | |
| // make it appear RGB. If on the other hand we ask for RGBA nd get RGB we | |
| // can't do anything about that. | |
| if (!surfaceless_) { | |
| GLint depth_bits = 0; | |
| GLint stencil_bits = 0; | |
| api()->glGetIntegervFn(GL_ALPHA_BITS, &alpha_bits); | |
| api()->glGetIntegervFn(GL_DEPTH_BITS, &depth_bits); | |
| api()->glGetIntegervFn(GL_STENCIL_BITS, &stencil_bits); | |
| back_buffer_color_format_ = alpha_bits > 0 ? GL_RGBA : GL_RGB; | |
| back_buffer_has_depth_ = depth_bits > 0; | |
| back_buffer_has_stencil_ = stencil_bits > 0; | |
| num_stencil_bits_ = stencil_bits; | |
| } else { | |
| num_stencil_bits_ = 0; | |
| } | |
| state_.viewport_width = surface->GetSize().width(); | |
| state_.viewport_height = surface->GetSize().height(); | |
| } | |
| GLint viewport_params[4] = {}; | |
| api()->glGetIntegervFn(GL_MAX_VIEWPORT_DIMS, viewport_params); | |
| viewport_max_width_ = viewport_params[0]; | |
| viewport_max_height_ = viewport_params[1]; | |
| api()->glGetFloatvFn(GL_ALIASED_LINE_WIDTH_RANGE, line_width_range_); | |
| state_.SetLineWidthBounds(line_width_range_[0], line_width_range_[1]); | |
| if (feature_info_->feature_flags().ext_window_rectangles) { | |
| GLint max_window_rectangles = -1; | |
| api()->glGetIntegervFn(GL_MAX_WINDOW_RECTANGLES_EXT, | |
| &max_window_rectangles); | |
| state_.SetMaxWindowRectangles(max_window_rectangles); | |
| } | |
| state_.scissor_width = state_.viewport_width; | |
| state_.scissor_height = state_.viewport_height; | |
| // Set all the default state because some GL drivers get it wrong. | |
| state_.InitCapabilities(nullptr); | |
| state_.InitState(nullptr); | |
| // Default state must be set before offscreen resources can be created. | |
| if (offscreen) { | |
| // Create the target frame buffer. This is the one that the client renders | |
| // directly to. | |
| offscreen_target_frame_buffer_ = std::make_unique<BackFramebuffer>(this); | |
| offscreen_target_frame_buffer_->Create(); | |
| offscreen_target_color_texture_ = std::make_unique<BackTexture>(this); | |
| offscreen_target_color_texture_->Create(); | |
| // Allocate the render buffers at their initial size and check the status | |
| // of the frame buffers is okay. | |
| if (!ResizeOffscreenFramebuffer( | |
| gfx::Size(state_.viewport_width, state_.viewport_height))) { | |
| LOG(ERROR) << "ContextResult::kFatalFailure: " | |
| "Could not allocate offscreen buffer storage."; | |
| return gpu::ContextResult::kFatalFailure; | |
| } | |
| } | |
| api()->glActiveTextureFn(GL_TEXTURE0 + state_.active_texture_unit); | |
| DoBindBuffer(GL_ARRAY_BUFFER, 0); | |
| DoBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); | |
| DoBindFramebuffer(GL_FRAMEBUFFER, 0); | |
| DoBindRenderbuffer(GL_RENDERBUFFER, 0); | |
| UpdateFramebufferSRGB(nullptr); | |
| bool call_gl_clear = !surfaceless_ && !offscreen; | |
| // Temporary workaround for Android WebView because this clear ignores the | |
| // clip and corrupts that external UI of the App. Not calling glClear is ok | |
| // because the system already clears the buffer before each draw. Proper | |
| // fix might be setting the scissor clip properly before initialize. See | |
| // crbug.com/259023 for details. | |
| call_gl_clear = surface_->GetHandle(); | |
| if (call_gl_clear) { | |
| // On configs where we report no alpha, if the underlying surface has | |
| // alpha, clear the surface alpha to 1.0 to be correct on ReadPixels/etc. | |
| bool clear_alpha = back_buffer_color_format_ == GL_RGB && alpha_bits > 0; | |
| if (clear_alpha) { | |
| api()->glClearColorFn(0.0f, 0.0f, 0.0f, 1.0f); | |
| } | |
| // Clear the backbuffer. | |
| api()->glClearFn(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | | |
| GL_STENCIL_BUFFER_BIT); | |
| // Restore alpha clear value if we changed it. | |
| if (clear_alpha) { | |
| api()->glClearColorFn(0.0f, 0.0f, 0.0f, 0.0f); | |
| } | |
| } | |
| supports_async_swap_ = surface->SupportsAsyncSwap(); | |
| if (workarounds().unbind_fbo_on_context_switch) { | |
| context_->SetUnbindFboOnMakeCurrent(); | |
| } | |
| if (workarounds().gl_clear_broken) { | |
| DCHECK(!clear_framebuffer_blit_.get()); | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER("glClearWorkaroundInit"); | |
| clear_framebuffer_blit_ = | |
| std::make_unique<ClearFramebufferResourceManager>(this); | |
| if (LOCAL_PEEK_GL_ERROR("glClearWorkaroundInit") != GL_NO_ERROR) { | |
| LOG(ERROR) << "ContextResult::kFatalFailure: " | |
| "glClearWorkaroundInit failed"; | |
| return gpu::ContextResult::kFatalFailure; | |
| } | |
| } | |
| if (feature_info_->context_type() == CONTEXT_TYPE_WEBGL2) { | |
| // If WebGL 2, the PRIMITIVE_RESTART_FIXED_INDEX should be always enabled. | |
| // See the section <Primitive Restart is Always Enabled> in WebGL 2 spec: | |
| // https://www.khronos.org/registry/webgl/specs/latest/2.0/#4.1.4 | |
| DoEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX); | |
| } | |
| if (group_->gpu_preferences().enable_gpu_driver_debug_logging && | |
| feature_info_->feature_flags().khr_debug) { | |
| InitializeGLDebugLogging(true, GLDebugMessageCallback, &logger_); | |
| } | |
| if (CheckResetStatus()) { | |
| // If the context was lost at any point before or during initialization, the | |
| // values queried from the driver could be bogus, and potentially | |
| // inconsistent between various ContextStates on the same underlying real GL | |
| // context. Make sure to report the failure early, to not allow virtualized | |
| // context switches in that case. | |
| LOG(ERROR) | |
| << " GLES2DecoderImpl: Context reset detected after initialization."; | |
| group_->LoseContexts(error::kUnknown); | |
| destroy_on_failure.LoseContext(); | |
| return gpu::ContextResult::kTransientFailure; | |
| } | |
| destroy_on_failure.OnSuccess(); | |
| return gpu::ContextResult::kSuccess; | |
| } | |
| Capabilities GLES2DecoderImpl::GetCapabilities() { | |
| DCHECK(initialized()); | |
| Capabilities caps; | |
| DoGetIntegerv(GL_MAX_TEXTURE_SIZE, &caps.max_texture_size, 1); | |
| if (workarounds().webgl_or_caps_max_texture_size) { | |
| caps.max_texture_size = | |
| std::min(caps.max_texture_size, | |
| feature_info_->workarounds().webgl_or_caps_max_texture_size); | |
| } | |
| caps.egl_image_external = | |
| feature_info_->feature_flags().oes_egl_image_external; | |
| caps.egl_image_external_essl3 = | |
| feature_info_->feature_flags().oes_egl_image_external_essl3; | |
| caps.texture_format_bgra8888 = | |
| feature_info_->feature_flags().ext_texture_format_bgra8888; | |
| caps.disable_mac_swangle_rgbx = | |
| feature_info_->feature_flags().disable_mac_swangle_rgbx; | |
| caps.texture_format_etc1_npot = | |
| feature_info_->feature_flags().oes_compressed_etc1_rgb8_texture && | |
| !workarounds().etc1_power_of_two_only; | |
| caps.sync_query = feature_info_->feature_flags().chromium_sync_query; | |
| // Only query the kEnableMSAAOnNewIntelGPUs feature flag if the host device | |
| // is affected by the experiment. | |
| bool eligible_for_experiment = | |
| workarounds().msaa_is_slow && !workarounds().msaa_is_slow_2; | |
| caps.msaa_is_slow = | |
| eligible_for_experiment | |
| ? !base::FeatureList::IsEnabled(features::kEnableMSAAOnNewIntelGPUs) | |
| : workarounds().msaa_is_slow; | |
| caps.avoid_stencil_buffers = workarounds().avoid_stencil_buffers; | |
| caps.texture_rg = feature_info_->feature_flags().ext_texture_rg; | |
| caps.texture_norm16 = feature_info_->feature_flags().ext_texture_norm16; | |
| caps.texture_half_float_linear = | |
| feature_info_->oes_texture_half_float_linear_available(); | |
| caps.image_ar30 = feature_info_->feature_flags().chromium_image_ar30; | |
| caps.image_ab30 = feature_info_->feature_flags().chromium_image_ab30; | |
| caps.render_buffer_format_bgra8888 = | |
| feature_info_->feature_flags().ext_render_buffer_format_bgra8888; | |
| caps.mesa_framebuffer_flip_y = | |
| feature_info_->feature_flags().mesa_framebuffer_flip_y; | |
| // Technically, YUV readback is handled on the client side, but enable it here | |
| // so that clients can use this to detect support. | |
| caps.supports_yuv_readback = true; | |
| return caps; | |
| } | |
| GLCapabilities GLES2DecoderImpl::GetGLCapabilities() { | |
| CHECK(initialized()); | |
| GLCapabilities caps; | |
| caps.VisitPrecisions([](GLenum shader, GLenum type, | |
| GLCapabilities::ShaderPrecision* shader_precision) { | |
| GLint range[2] = {0, 0}; | |
| GLint precision = 0; | |
| QueryShaderPrecisionFormat(shader, type, range, &precision); | |
| shader_precision->min_range = range[0]; | |
| shader_precision->max_range = range[1]; | |
| shader_precision->precision = precision; | |
| }); | |
| if (feature_info_->IsWebGL2OrES3Context()) { | |
| // TODO(zmo): once we switch to MANGLE, we should query version numbers. | |
| caps.major_version = 3; | |
| caps.minor_version = 0; | |
| } | |
| DoGetIntegerv(GL_MAX_TEXTURE_SIZE, &caps.max_texture_size, 1); | |
| if (workarounds().webgl_or_caps_max_texture_size) { | |
| caps.max_texture_size = | |
| std::min(caps.max_texture_size, | |
| feature_info_->workarounds().webgl_or_caps_max_texture_size); | |
| } | |
| DoGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, | |
| &caps.max_combined_texture_image_units, 1); | |
| DoGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &caps.max_cube_map_texture_size, | |
| 1); | |
| DoGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_VECTORS, | |
| &caps.max_fragment_uniform_vectors, 1); | |
| DoGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &caps.max_renderbuffer_size, 1); | |
| DoGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &caps.max_texture_image_units, 1); | |
| DoGetIntegerv(GL_MAX_VARYING_VECTORS, &caps.max_varying_vectors, 1); | |
| DoGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &caps.max_vertex_attribs, 1); | |
| DoGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, | |
| &caps.max_vertex_texture_image_units, 1); | |
| DoGetIntegerv(GL_MAX_VERTEX_UNIFORM_VECTORS, &caps.max_vertex_uniform_vectors, | |
| 1); | |
| { | |
| GLint dims[2] = {0, 0}; | |
| DoGetIntegerv(GL_MAX_VIEWPORT_DIMS, dims, 2); | |
| caps.max_viewport_width = dims[0]; | |
| caps.max_viewport_height = dims[1]; | |
| } | |
| DoGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, | |
| &caps.num_compressed_texture_formats, 1); | |
| DoGetIntegerv(GL_NUM_SHADER_BINARY_FORMATS, &caps.num_shader_binary_formats, | |
| 1); | |
| if (feature_info_->IsWebGL2OrES3Context()) { | |
| // TODO(zmo): Note that some parameter values could be more than 32-bit, | |
| // but for now we clamp them to 32-bit max. | |
| DoGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &caps.max_3d_texture_size, 1); | |
| DoGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &caps.max_array_texture_layers, | |
| 1); | |
| DoGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &caps.max_color_attachments, 1); | |
| DoGetInteger64v(GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS, | |
| &caps.max_combined_fragment_uniform_components, 1); | |
| DoGetIntegerv(GL_MAX_COMBINED_UNIFORM_BLOCKS, | |
| &caps.max_combined_uniform_blocks, 1); | |
| DoGetInteger64v(GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS, | |
| &caps.max_combined_vertex_uniform_components, 1); | |
| DoGetIntegerv(GL_MAX_DRAW_BUFFERS, &caps.max_draw_buffers, 1); | |
| DoGetInteger64v(GL_MAX_ELEMENT_INDEX, &caps.max_element_index, 1); | |
| DoGetIntegerv(GL_MAX_ELEMENTS_INDICES, &caps.max_elements_indices, 1); | |
| DoGetIntegerv(GL_MAX_ELEMENTS_VERTICES, &caps.max_elements_vertices, 1); | |
| DoGetIntegerv(GL_MAX_FRAGMENT_INPUT_COMPONENTS, | |
| &caps.max_fragment_input_components, 1); | |
| DoGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_BLOCKS, | |
| &caps.max_fragment_uniform_blocks, 1); | |
| DoGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, | |
| &caps.max_fragment_uniform_components, 1); | |
| DoGetIntegerv(GL_MAX_PROGRAM_TEXEL_OFFSET, &caps.max_program_texel_offset, | |
| 1); | |
| DoGetInteger64v(GL_MAX_SERVER_WAIT_TIMEOUT, &caps.max_server_wait_timeout, | |
| 1); | |
| // Work around Linux NVIDIA driver bug where GL_TIMEOUT_IGNORED is | |
| // returned. | |
| if (caps.max_server_wait_timeout < 0) { | |
| caps.max_server_wait_timeout = 0; | |
| } | |
| DoGetFloatv(GL_MAX_TEXTURE_LOD_BIAS, &caps.max_texture_lod_bias, 1); | |
| DoGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS, | |
| &caps.max_transform_feedback_interleaved_components, 1); | |
| DoGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, | |
| &caps.max_transform_feedback_separate_attribs, 1); | |
| DoGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS, | |
| &caps.max_transform_feedback_separate_components, 1); | |
| DoGetInteger64v(GL_MAX_UNIFORM_BLOCK_SIZE, &caps.max_uniform_block_size, 1); | |
| DoGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, | |
| &caps.max_uniform_buffer_bindings, 1); | |
| DoGetIntegerv(GL_MAX_VARYING_COMPONENTS, &caps.max_varying_components, 1); | |
| DoGetIntegerv(GL_MAX_VERTEX_OUTPUT_COMPONENTS, | |
| &caps.max_vertex_output_components, 1); | |
| DoGetIntegerv(GL_MAX_VERTEX_UNIFORM_BLOCKS, &caps.max_vertex_uniform_blocks, | |
| 1); | |
| DoGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, | |
| &caps.max_vertex_uniform_components, 1); | |
| DoGetIntegerv(GL_MIN_PROGRAM_TEXEL_OFFSET, &caps.min_program_texel_offset, | |
| 1); | |
| // TODO(vmiura): Remove GL_NUM_PROGRAM_BINARY_FORMATS. | |
| DoGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, | |
| &caps.num_program_binary_formats, 1); | |
| DoGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, | |
| &caps.uniform_buffer_offset_alignment, 1); | |
| } | |
| if (feature_info_->feature_flags().multisampled_render_to_texture || | |
| feature_info_->feature_flags().chromium_framebuffer_multisample || | |
| feature_info_->IsWebGL2OrES3Context()) { | |
| caps.max_samples = ComputeMaxSamples(); | |
| } | |
| caps.occlusion_query_boolean = | |
| feature_info_->feature_flags().occlusion_query_boolean; | |
| caps.timer_queries = query_manager_->GPUTimingAvailable(); | |
| caps.sync_query = feature_info_->feature_flags().chromium_sync_query; | |
| return caps; | |
| } | |
| GLint GLES2DecoderImpl::ComputeMaxSamples() { | |
| GLint max_samples = 0; | |
| DoGetIntegerv(GL_MAX_SAMPLES, &max_samples, 1); | |
| if (feature_info_->IsWebGLContext() && | |
| feature_info_->feature_flags().nv_internalformat_sample_query) { | |
| std::vector<GLint> temp; | |
| auto minWithSamplesForFormat = [&](GLenum internalformat) { | |
| temp.clear(); | |
| InternalFormatSampleCountsHelper(GL_RENDERBUFFER, internalformat, &temp); | |
| max_samples = std::min(max_samples, temp[0]); | |
| }; | |
| // OpenGL ES 3.0.5, section 4.4.2.2: "Implementations must support creation | |
| // of renderbuffers in these required formats with up to the value of | |
| // MAX_SAMPLES multisamples, with the exception of signed and unsigned | |
| // integer formats." | |
| // OpenGL ES 3.0.5, section 3.8.3.1 | |
| minWithSamplesForFormat(GL_RGBA8); | |
| minWithSamplesForFormat(GL_SRGB8_ALPHA8); | |
| minWithSamplesForFormat(GL_RGB10_A2); | |
| minWithSamplesForFormat(GL_RGBA4); | |
| minWithSamplesForFormat(GL_RGB5_A1); | |
| minWithSamplesForFormat(GL_RGB8); | |
| minWithSamplesForFormat(GL_RGB565); | |
| minWithSamplesForFormat(GL_RG8); | |
| minWithSamplesForFormat(GL_R8); | |
| } | |
| return max_samples; | |
| } | |
| void GLES2DecoderImpl::UpdateCapabilities() { | |
| util_.set_num_compressed_texture_formats( | |
| validators_->compressed_texture_format.GetValues().size()); | |
| util_.set_num_shader_binary_formats( | |
| validators_->shader_binary_format.GetValues().size()); | |
| } | |
| bool GLES2DecoderImpl::InitializeShaderTranslator() { | |
| TRACE_EVENT0("gpu", "GLES2DecoderImpl::InitializeShaderTranslator"); | |
| if (feature_info_->disable_shader_translator()) { | |
| return true; | |
| } | |
| if (vertex_translator_ || fragment_translator_) { | |
| DCHECK(vertex_translator_ && fragment_translator_); | |
| return true; | |
| } | |
| ShBuiltInResources resources; | |
| sh::InitBuiltInResources(&resources); | |
| resources.MaxVertexAttribs = group_->max_vertex_attribs(); | |
| resources.MaxVertexUniformVectors = | |
| group_->max_vertex_uniform_vectors(); | |
| resources.MaxVaryingVectors = group_->max_varying_vectors(); | |
| resources.MaxVertexTextureImageUnits = | |
| group_->max_vertex_texture_image_units(); | |
| resources.MaxCombinedTextureImageUnits = group_->max_texture_units(); | |
| resources.MaxTextureImageUnits = group_->max_texture_image_units(); | |
| resources.MaxFragmentUniformVectors = | |
| group_->max_fragment_uniform_vectors(); | |
| resources.MaxDrawBuffers = group_->max_draw_buffers(); | |
| resources.MaxExpressionComplexity = 256; | |
| resources.MaxCallStackDepth = 256; | |
| resources.MaxDualSourceDrawBuffers = group_->max_dual_source_draw_buffers(); | |
| if (!feature_info_->IsWebGL1OrES2Context()) { | |
| resources.MaxVertexOutputVectors = | |
| group_->max_vertex_output_components() / 4; | |
| resources.MaxFragmentInputVectors = | |
| group_->max_fragment_input_components() / 4; | |
| resources.MaxProgramTexelOffset = group_->max_program_texel_offset(); | |
| resources.MinProgramTexelOffset = group_->min_program_texel_offset(); | |
| } | |
| resources.EXT_YUV_target = features().ext_yuv_target ? 1 : 0; | |
| ShShaderSpec shader_spec; | |
| switch (feature_info_->context_type()) { | |
| case CONTEXT_TYPE_WEBGL1: | |
| shader_spec = SH_WEBGL_SPEC; | |
| resources.OES_standard_derivatives = derivatives_explicitly_enabled_; | |
| resources.ARB_texture_rectangle = | |
| (features().arb_texture_rectangle && arb_texture_rectangle_enabled_) | |
| ? 1 | |
| : 0; | |
| resources.OES_EGL_image_external = | |
| (features().oes_egl_image_external && oes_egl_image_external_enabled_) | |
| ? 1 | |
| : 0; | |
| resources.NV_EGL_stream_consumer_external = | |
| (features().nv_egl_stream_consumer_external && | |
| nv_egl_stream_consumer_external_enabled_) | |
| ? 1 | |
| : 0; | |
| resources.EXT_frag_depth = frag_depth_explicitly_enabled_; | |
| resources.EXT_draw_buffers = draw_buffers_explicitly_enabled_; | |
| if (!draw_buffers_explicitly_enabled_) | |
| resources.MaxDrawBuffers = 1; | |
| resources.EXT_shader_texture_lod = shader_texture_lod_explicitly_enabled_; | |
| resources.NV_draw_buffers = | |
| draw_buffers_explicitly_enabled_ && features().nv_draw_buffers; | |
| break; | |
| case CONTEXT_TYPE_WEBGL2: | |
| shader_spec = SH_WEBGL2_SPEC; | |
| resources.ARB_texture_rectangle = | |
| (features().arb_texture_rectangle && arb_texture_rectangle_enabled_) | |
| ? 1 | |
| : 0; | |
| resources.OES_EGL_image_external = | |
| (features().oes_egl_image_external && oes_egl_image_external_enabled_) | |
| ? 1 | |
| : 0; | |
| resources.NV_EGL_stream_consumer_external = | |
| (features().nv_egl_stream_consumer_external && | |
| nv_egl_stream_consumer_external_enabled_) | |
| ? 1 | |
| : 0; | |
| break; | |
| case CONTEXT_TYPE_OPENGLES2: | |
| // Map OpenGLES contexts to WebGL shader specs to enforce WebGL-specific | |
| // safety mitigations and shader sanitization. | |
| shader_spec = SH_WEBGL_SPEC; | |
| resources.OES_standard_derivatives = | |
| features().oes_standard_derivatives ? 1 : 0; | |
| resources.ARB_texture_rectangle = | |
| features().arb_texture_rectangle ? 1 : 0; | |
| resources.OES_EGL_image_external = | |
| features().oes_egl_image_external ? 1 : 0; | |
| resources.NV_EGL_stream_consumer_external = | |
| features().nv_egl_stream_consumer_external ? 1 : 0; | |
| resources.EXT_draw_buffers = | |
| features().ext_draw_buffers ? 1 : 0; | |
| resources.EXT_frag_depth = | |
| features().ext_frag_depth ? 1 : 0; | |
| resources.EXT_shader_texture_lod = | |
| features().ext_shader_texture_lod ? 1 : 0; | |
| resources.NV_draw_buffers = | |
| features().nv_draw_buffers ? 1 : 0; | |
| resources.EXT_blend_func_extended = | |
| features().ext_blend_func_extended ? 1 : 0; | |
| break; | |
| case CONTEXT_TYPE_OPENGLES3: | |
| // Map OpenGLES contexts to WebGL shader specs to enforce WebGL-specific | |
| // safety mitigations and shader sanitization. | |
| shader_spec = SH_WEBGL2_SPEC; | |
| resources.ARB_texture_rectangle = | |
| features().arb_texture_rectangle ? 1 : 0; | |
| resources.OES_EGL_image_external = | |
| features().oes_egl_image_external ? 1 : 0; | |
| resources.NV_EGL_stream_consumer_external = | |
| features().nv_egl_stream_consumer_external ? 1 : 0; | |
| resources.EXT_blend_func_extended = | |
| features().ext_blend_func_extended ? 1 : 0; | |
| break; | |
| default: | |
| NOTREACHED(); | |
| } | |
| if (feature_info_->IsWebGLContext()) { | |
| resources.ANGLE_multi_draw = | |
| multi_draw_explicitly_enabled_ && features().webgl_multi_draw; | |
| } | |
| if (feature_info_->context_type() == CONTEXT_TYPE_WEBGL2) { | |
| // The gl_BaseVertex/BaseInstance shader builtins is disabled in ANGLE for | |
| // WebGL As they are removed in | |
| // https://github.com/KhronosGroup/WebGL/pull/3278 | |
| // To re-enable the shader | |
| // builtins add back SH_EMULATE_GL_BASE_VERTEX_BASE_INSTANCE to | |
| // ShCompileOptions in ANGLE | |
| resources.ANGLE_base_vertex_base_instance_shader_builtin = | |
| (draw_instanced_base_vertex_base_instance_explicitly_enabled_ && | |
| features().webgl_draw_instanced_base_vertex_base_instance) || | |
| (multi_draw_instanced_base_vertex_base_instance_explicitly_enabled_ && | |
| features().webgl_multi_draw_instanced_base_vertex_base_instance); | |
| } | |
| if ((feature_info_->IsWebGLContext() && | |
| features().enable_shader_name_hashing) || | |
| force_shader_name_hashing_for_test) { | |
| // TODO(crbug.com/40601370): In theory, it should be OK to change this | |
| // hash. However, in practice, this seems to cause some tests to fail. See | |
| // https://crbug.com/963889. | |
| resources.HashFunction = +[](const char* data, size_t size) { | |
| return base::legacy::CityHash64( | |
| base::as_bytes(UNSAFE_TODO(base::span(data, size)))); | |
| }; | |
| } else { | |
| resources.HashFunction = nullptr; | |
| } | |
| ShCompileOptions driver_bug_workarounds{}; | |
| if (workarounds().init_gl_position_in_vertex_shader) | |
| driver_bug_workarounds.initGLPosition = true; | |
| if (workarounds().scalarize_vec_and_mat_constructor_args) | |
| driver_bug_workarounds.scalarizeVecAndMatConstructorArgs = true; | |
| if (workarounds().dont_use_loops_to_initialize_variables) | |
| driver_bug_workarounds.dontUseLoopsToInitializeVariables = true; | |
| if (workarounds().remove_dynamic_indexing_of_swizzled_vector) | |
| driver_bug_workarounds.removeDynamicIndexingOfSwizzledVector = true; | |
| if (workarounds().validate_max_per_stage_uniform_blocks_at_compile_time) { | |
| driver_bug_workarounds.validatePerStageMaxUniformBlocks = true; | |
| } | |
| if (workarounds().limit_output_varyings_at_compile_time) { | |
| driver_bug_workarounds.limitOutputVaryingsTo256 = true; | |
| } | |
| // Initialize uninitialized locals and shared variables by default | |
| driver_bug_workarounds.initializeUninitializedLocals = true; | |
| driver_bug_workarounds.initSharedVariables = true; | |
| // Harden against excessive expressions and undefined behavior | |
| driver_bug_workarounds.rejectWebglShadersWithLargeVariables = true; | |
| driver_bug_workarounds.rejectWebglShadersWithUndefinedBehavior = true; | |
| driver_bug_workarounds.limitCallStackDepth = true; | |
| driver_bug_workarounds.limitExpressionComplexity = true; | |
| vertex_translator_ = shader_translator_cache()->GetTranslator( | |
| GL_VERTEX_SHADER, shader_spec, &resources, SH_ESSL_OUTPUT, | |
| driver_bug_workarounds); | |
| if (!vertex_translator_.get()) { | |
| LOG(ERROR) << "Could not initialize vertex shader translator."; | |
| Destroy(true); | |
| return false; | |
| } | |
| fragment_translator_ = shader_translator_cache()->GetTranslator( | |
| GL_FRAGMENT_SHADER, shader_spec, &resources, SH_ESSL_OUTPUT, | |
| driver_bug_workarounds); | |
| if (!fragment_translator_.get()) { | |
| LOG(ERROR) << "Could not initialize fragment shader translator."; | |
| Destroy(true); | |
| return false; | |
| } | |
| return true; | |
| } | |
| void GLES2DecoderImpl::DestroyShaderTranslator() { | |
| vertex_translator_ = nullptr; | |
| fragment_translator_ = nullptr; | |
| } | |
| bool GLES2DecoderImpl::GenBuffersHelper(GLsizei n, const GLuint* client_ids) { | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| if (GetBuffer(UNSAFE_TODO(client_ids[ii]))) { | |
| return false; | |
| } | |
| } | |
| auto service_ids = base::HeapArray<GLuint>::Uninit(n); | |
| api()->glGenBuffersARBFn(n, service_ids.data()); | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| CreateBuffer(UNSAFE_TODO(client_ids[ii]), service_ids[ii]); | |
| } | |
| return true; | |
| } | |
| bool GLES2DecoderImpl::GenFramebuffersHelper( | |
| GLsizei n, const GLuint* client_ids) { | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| if (GetFramebuffer(UNSAFE_TODO(client_ids[ii]))) { | |
| return false; | |
| } | |
| } | |
| auto service_ids = base::HeapArray<GLuint>::Uninit(n); | |
| api()->glGenFramebuffersEXTFn(n, service_ids.data()); | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| CreateFramebuffer(UNSAFE_TODO(client_ids[ii]), service_ids[ii]); | |
| } | |
| return true; | |
| } | |
| bool GLES2DecoderImpl::GenRenderbuffersHelper( | |
| GLsizei n, const GLuint* client_ids) { | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| if (GetRenderbuffer(UNSAFE_TODO(client_ids[ii]))) { | |
| return false; | |
| } | |
| } | |
| auto service_ids = base::HeapArray<GLuint>::Uninit(n); | |
| api()->glGenRenderbuffersEXTFn(n, service_ids.data()); | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| CreateRenderbuffer(UNSAFE_TODO(client_ids[ii]), service_ids[ii]); | |
| } | |
| return true; | |
| } | |
| bool GLES2DecoderImpl::GenTexturesHelper(GLsizei n, const GLuint* client_ids) { | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| if (GetTexture(UNSAFE_TODO(client_ids[ii]))) { | |
| return false; | |
| } | |
| } | |
| auto service_ids = base::HeapArray<GLuint>::Uninit(n); | |
| api()->glGenTexturesFn(n, service_ids.data()); | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| CreateTexture(UNSAFE_TODO(client_ids[ii]), service_ids[ii]); | |
| } | |
| return true; | |
| } | |
| bool GLES2DecoderImpl::GenSamplersHelper(GLsizei n, const GLuint* client_ids) { | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| if (GetSampler(UNSAFE_TODO(client_ids[ii]))) { | |
| return false; | |
| } | |
| } | |
| auto service_ids = base::HeapArray<GLuint>::Uninit(n); | |
| api()->glGenSamplersFn(n, service_ids.data()); | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| CreateSampler(UNSAFE_TODO(client_ids[ii]), service_ids[ii]); | |
| } | |
| return true; | |
| } | |
| bool GLES2DecoderImpl::GenTransformFeedbacksHelper( | |
| GLsizei n, const GLuint* client_ids) { | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| if (GetTransformFeedback(UNSAFE_TODO(client_ids[ii]))) { | |
| return false; | |
| } | |
| } | |
| auto service_ids = base::HeapArray<GLuint>::Uninit(n); | |
| api()->glGenTransformFeedbacksFn(n, service_ids.data()); | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| CreateTransformFeedback(UNSAFE_TODO(client_ids[ii]), service_ids[ii]); | |
| } | |
| return true; | |
| } | |
| void GLES2DecoderImpl::DeleteBuffersHelper(GLsizei n, | |
| const volatile GLuint* client_ids) { | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| GLuint client_id = UNSAFE_TODO(client_ids[ii]); | |
| Buffer* buffer = GetBuffer(client_id); | |
| if (buffer && !buffer->IsDeleted()) { | |
| state_.RemoveBoundBuffer(buffer); | |
| buffer_manager()->RemoveBuffer(client_id); | |
| } | |
| } | |
| } | |
| void GLES2DecoderImpl::FlushQueriesBeforeDeletingOrUnbindingFboWorkaround() { | |
| if (workarounds().flush_queries_before_deleting_or_unbinding_fbo && | |
| HasPendingQueries()) { | |
| GLsync sync = api()->glFenceSyncFn(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); | |
| if (sync) { | |
| api()->glClientWaitSyncFn(sync, GL_SYNC_FLUSH_COMMANDS_BIT, 0); | |
| api()->glDeleteSyncFn(sync); | |
| } else { | |
| api()->glFinishFn(); | |
| } | |
| } | |
| } | |
| void GLES2DecoderImpl::DeleteFramebuffersHelper( | |
| GLsizei n, | |
| const volatile GLuint* client_ids) { | |
| // Flush queries that may be dependent on the FBO. | |
| FlushQueriesBeforeDeletingOrUnbindingFboWorkaround(); | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| GLuint client_id = UNSAFE_TODO(client_ids[ii]); | |
| Framebuffer* framebuffer = GetFramebuffer(client_id); | |
| if (framebuffer && !framebuffer->IsDeleted()) { | |
| if (framebuffer == framebuffer_state_.bound_draw_framebuffer.get()) { | |
| GLenum target = GetDrawFramebufferTarget(); | |
| // Unbind attachments on FBO before deletion. | |
| if (workarounds().unbind_attachments_on_bound_render_fbo_delete) | |
| framebuffer->DoUnbindGLAttachmentsForWorkaround(target); | |
| BindFramebuffer(target, GetBackbufferServiceId()); | |
| state_.UpdateWindowRectanglesForBoundDrawFramebufferClientID(0); | |
| framebuffer_state_.bound_draw_framebuffer = nullptr; | |
| framebuffer_state_.clear_state_dirty = true; | |
| } | |
| if (framebuffer == framebuffer_state_.bound_read_framebuffer.get()) { | |
| framebuffer_state_.bound_read_framebuffer = nullptr; | |
| GLenum target = GetReadFramebufferTarget(); | |
| BindFramebuffer(target, GetBackbufferServiceId()); | |
| } | |
| OnFboChanged(); | |
| RemoveFramebuffer(client_id); | |
| } | |
| } | |
| } | |
| void GLES2DecoderImpl::DeleteRenderbuffersHelper( | |
| GLsizei n, | |
| const volatile GLuint* client_ids) { | |
| bool supports_separate_framebuffer_binds = | |
| features().chromium_framebuffer_multisample; | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| GLuint client_id = UNSAFE_TODO(client_ids[ii]); | |
| Renderbuffer* renderbuffer = GetRenderbuffer(client_id); | |
| if (renderbuffer && !renderbuffer->IsDeleted()) { | |
| if (state_.bound_renderbuffer.get() == renderbuffer) { | |
| state_.bound_renderbuffer = nullptr; | |
| } | |
| // Unbind from current framebuffers. | |
| if (supports_separate_framebuffer_binds) { | |
| if (framebuffer_state_.bound_read_framebuffer.get()) { | |
| framebuffer_state_.bound_read_framebuffer->UnbindRenderbuffer( | |
| GL_READ_FRAMEBUFFER, renderbuffer); | |
| } | |
| if (framebuffer_state_.bound_draw_framebuffer.get()) { | |
| framebuffer_state_.bound_draw_framebuffer->UnbindRenderbuffer( | |
| GL_DRAW_FRAMEBUFFER, renderbuffer); | |
| } | |
| } else { | |
| if (framebuffer_state_.bound_draw_framebuffer.get()) { | |
| framebuffer_state_.bound_draw_framebuffer | |
| ->UnbindRenderbuffer(GL_FRAMEBUFFER, renderbuffer); | |
| } | |
| } | |
| framebuffer_state_.clear_state_dirty = true; | |
| RemoveRenderbuffer(client_id); | |
| } | |
| } | |
| } | |
| void GLES2DecoderImpl::DeleteTexturesHelper(GLsizei n, | |
| const volatile GLuint* client_ids) { | |
| bool supports_separate_framebuffer_binds = SupportsSeparateFramebufferBinds(); | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| GLuint client_id = UNSAFE_TODO(client_ids[ii]); | |
| TextureRef* texture_ref = GetTexture(client_id); | |
| if (texture_ref) { | |
| UnbindTexture(texture_ref, supports_separate_framebuffer_binds); | |
| RemoveTexture(client_id); | |
| } | |
| } | |
| } | |
| void GLES2DecoderImpl::DeleteSamplersHelper(GLsizei n, | |
| const volatile GLuint* client_ids) { | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| GLuint client_id = UNSAFE_TODO(client_ids[ii]); | |
| Sampler* sampler = GetSampler(client_id); | |
| if (sampler && !sampler->IsDeleted()) { | |
| // Unbind from current sampler units. | |
| state_.UnbindSampler(sampler); | |
| RemoveSampler(client_id); | |
| } | |
| } | |
| } | |
| void GLES2DecoderImpl::DeleteTransformFeedbacksHelper( | |
| GLsizei n, | |
| const volatile GLuint* client_ids) { | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| GLuint client_id = UNSAFE_TODO(client_ids[ii]); | |
| TransformFeedback* transform_feedback = GetTransformFeedback(client_id); | |
| if (transform_feedback) { | |
| if (transform_feedback->active()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glDeleteTransformFeedbacks", | |
| "Deleting transform feedback is active"); | |
| return; | |
| } | |
| if (state_.bound_transform_feedback.get() == transform_feedback) { | |
| // Bind to the default transform feedback. | |
| DCHECK(state_.default_transform_feedback.get()); | |
| state_.default_transform_feedback->DoBindTransformFeedback( | |
| GL_TRANSFORM_FEEDBACK, state_.bound_transform_feedback.get(), | |
| state_.bound_transform_feedback_buffer.get()); | |
| state_.bound_transform_feedback = | |
| state_.default_transform_feedback.get(); | |
| } | |
| RemoveTransformFeedback(client_id); | |
| } | |
| } | |
| } | |
| void GLES2DecoderImpl::DeleteSyncHelper(GLuint sync) { | |
| GLsync service_id = 0; | |
| if (group_->GetSyncServiceId(sync, &service_id)) { | |
| api()->glDeleteSyncFn(service_id); | |
| group_->RemoveSyncId(sync); | |
| } else if (sync != 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glDeleteSync", "unknown sync"); | |
| } | |
| } | |
| bool GLES2DecoderImpl::MakeCurrent() { | |
| if (!context_.get()) | |
| return false; | |
| if (WasContextLost()) { | |
| LOG(ERROR) << " GLES2DecoderImpl: Trying to make lost context current."; | |
| return false; | |
| } | |
| if (!context_->MakeCurrent(surface_.get())) { | |
| LOG(ERROR) << " GLES2DecoderImpl: Context lost during MakeCurrent."; | |
| MarkContextLost(error::kMakeCurrentFailed); | |
| group_->LoseContexts(error::kUnknown); | |
| return false; | |
| } | |
| DCHECK_EQ(api(), gl::g_current_gl_context); | |
| if (CheckResetStatus()) { | |
| LOG(ERROR) | |
| << " GLES2DecoderImpl: Context reset detected after MakeCurrent."; | |
| group_->LoseContexts(error::kUnknown); | |
| return false; | |
| } | |
| ProcessFinishedAsyncTransfers(); | |
| // Rebind the FBO if it was unbound by the context. | |
| if (workarounds().unbind_fbo_on_context_switch) | |
| RestoreFramebufferBindings(); | |
| framebuffer_state_.clear_state_dirty = true; | |
| state_.stencil_state_changed_since_validation = true; | |
| // Rebind textures if the service ids may have changed. | |
| RestoreAllExternalTextureBindingsIfNeeded(); | |
| return true; | |
| } | |
| void GLES2DecoderImpl::ProcessFinishedAsyncTransfers() { | |
| ProcessPendingReadPixels(false); | |
| } | |
| void GLES2DecoderImpl::RestoreCurrentFramebufferBindings() { | |
| framebuffer_state_.clear_state_dirty = true; | |
| auto rebind_current_framebuffer = [this](GLenum target, | |
| Framebuffer* framebuffer, | |
| GLuint back_buffer_service_id) { | |
| GLuint framebuffer_id = framebuffer ? framebuffer->service_id() : 0; | |
| if (framebuffer_id == 0) { | |
| framebuffer_id = back_buffer_service_id; | |
| } | |
| BindFramebuffer(target, framebuffer_id); | |
| }; | |
| if (!SupportsSeparateFramebufferBinds()) { | |
| rebind_current_framebuffer(GL_FRAMEBUFFER, | |
| framebuffer_state_.bound_draw_framebuffer.get(), | |
| GetBackbufferServiceId()); | |
| } else { | |
| rebind_current_framebuffer(GL_READ_FRAMEBUFFER, | |
| framebuffer_state_.bound_read_framebuffer.get(), | |
| GetBackbufferServiceId()); | |
| rebind_current_framebuffer(GL_DRAW_FRAMEBUFFER, | |
| framebuffer_state_.bound_draw_framebuffer.get(), | |
| GetBackbufferServiceId()); | |
| } | |
| OnFboChanged(); | |
| } | |
| bool GLES2DecoderImpl::CheckFramebufferValid( | |
| Framebuffer* framebuffer, | |
| GLenum target, | |
| GLenum gl_error, | |
| const char* func_name) { | |
| if (!framebuffer) { | |
| if (surfaceless_) | |
| return false; | |
| if (backbuffer_needs_clear_bits_) { | |
| // glClear and glDrawBuffers operate on GL_DRAW_FRAMEBUFFER, so make | |
| // sure the backbuffer is bound there before clearing it. | |
| Framebuffer* draw_framebuffer = GetBoundDrawFramebuffer(); | |
| if (draw_framebuffer) { | |
| BindFramebuffer(GL_DRAW_FRAMEBUFFER, GetBackbufferServiceId()); | |
| } | |
| api()->glClearColorFn(0, 0, 0, 1.0f); | |
| state_.SetDeviceColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); | |
| api()->glClearStencilFn(0); | |
| state_.SetDeviceStencilMaskSeparate(GL_FRONT, kDefaultStencilMask); | |
| state_.SetDeviceStencilMaskSeparate(GL_BACK, kDefaultStencilMask); | |
| api()->glClearDepthFn(1.0f); | |
| state_.SetDeviceDepthMask(GL_TRUE); | |
| state_.SetDeviceCapabilityState(GL_SCISSOR_TEST, false); | |
| ClearDeviceWindowRectangles(); | |
| bool reset_draw_buffer = false; | |
| if ((backbuffer_needs_clear_bits_ & GL_COLOR_BUFFER_BIT) != 0 && | |
| back_buffer_draw_buffer_ == GL_NONE) { | |
| reset_draw_buffer = true; | |
| GLenum buf = GL_BACK; | |
| if (GetBackbufferServiceId() != 0) // emulated backbuffer | |
| buf = GL_COLOR_ATTACHMENT0; | |
| api()->glDrawBuffersARBFn(1, &buf); | |
| } | |
| if (workarounds().gl_clear_broken) { | |
| ClearFramebufferForWorkaround(backbuffer_needs_clear_bits_); | |
| } else { | |
| api()->glClearFn(backbuffer_needs_clear_bits_); | |
| } | |
| if (reset_draw_buffer) { | |
| GLenum buf = GL_NONE; | |
| api()->glDrawBuffersARBFn(1, &buf); | |
| } | |
| backbuffer_needs_clear_bits_ = 0; | |
| RestoreClearState(); | |
| // Restore any previously bound GL_DRAW_FRAMEBUFFER. | |
| if (draw_framebuffer) { | |
| BindFramebuffer(GL_DRAW_FRAMEBUFFER, draw_framebuffer->service_id()); | |
| } | |
| } | |
| return true; | |
| } | |
| if (!framebuffer_manager()->IsComplete(framebuffer)) { | |
| GLenum completeness = framebuffer->IsPossiblyComplete(feature_info_.get()); | |
| if (completeness != GL_FRAMEBUFFER_COMPLETE) { | |
| LOCAL_SET_GL_ERROR(gl_error, func_name, "framebuffer incomplete"); | |
| return false; | |
| } | |
| if (framebuffer->GetStatus(texture_manager(), target) != | |
| GL_FRAMEBUFFER_COMPLETE) { | |
| LOCAL_SET_GL_ERROR( | |
| gl_error, func_name, "framebuffer incomplete (check)"); | |
| return false; | |
| } | |
| framebuffer_manager()->MarkAsComplete(framebuffer); | |
| } | |
| // Are all the attachments cleared? | |
| if (renderbuffer_manager()->HaveUnclearedRenderbuffers() || | |
| texture_manager()->HaveUnclearedMips()) { | |
| if (!framebuffer->IsCleared()) { | |
| if (!ClearUnclearedAttachments(target, framebuffer)) { | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| bool GLES2DecoderImpl::CheckBoundDrawFramebufferValid( | |
| const char* func_name, | |
| bool check_float_blending) { | |
| Framebuffer* framebuffer = GetBoundDrawFramebuffer(); | |
| bool valid = CheckFramebufferValid( | |
| framebuffer, GetDrawFramebufferTarget(), | |
| GL_INVALID_FRAMEBUFFER_OPERATION, func_name); | |
| if (!valid) | |
| return false; | |
| if (check_float_blending) { | |
| // only is true when called by DoMultiDrawArrays or DoMultiDrawElements | |
| if (framebuffer && state_.GetEnabled(GL_BLEND) && | |
| !features().ext_float_blend) { | |
| if (framebuffer->HasActiveFloat32ColorAttachment()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "GL_BLEND with floating-point color attachments " | |
| "requires the EXT_float_blend extension"); | |
| return false; | |
| } | |
| } | |
| } | |
| if (!SupportsSeparateFramebufferBinds()) | |
| OnUseFramebuffer(); | |
| UpdateFramebufferSRGB(framebuffer); | |
| return true; | |
| } | |
| void GLES2DecoderImpl::UpdateFramebufferSRGB(Framebuffer* framebuffer) { | |
| // Manually set the value of FRAMEBUFFER_SRGB based on the state that was set | |
| // by the client. | |
| bool needs_enable_disable_framebuffer_srgb = false; | |
| bool enable_framebuffer_srgb = true; | |
| if (feature_info_->feature_flags().ext_srgb_write_control) { | |
| needs_enable_disable_framebuffer_srgb = true; | |
| enable_framebuffer_srgb &= state_.GetEnabled(GL_FRAMEBUFFER_SRGB_EXT); | |
| } | |
| if (needs_enable_disable_framebuffer_srgb) | |
| state_.EnableDisableFramebufferSRGB(enable_framebuffer_srgb); | |
| } | |
| bool GLES2DecoderImpl::CheckBoundReadFramebufferValid( | |
| const char* func_name, GLenum gl_error) { | |
| Framebuffer* framebuffer = GetBoundReadFramebuffer(); | |
| bool valid = CheckFramebufferValid( | |
| framebuffer, GetReadFramebufferTarget(), gl_error, func_name); | |
| return valid; | |
| } | |
| bool GLES2DecoderImpl::CheckBoundFramebufferValid(const char* func_name) { | |
| GLenum gl_error = GL_INVALID_FRAMEBUFFER_OPERATION; | |
| Framebuffer* draw_framebuffer = GetBoundDrawFramebuffer(); | |
| bool valid = CheckFramebufferValid( | |
| draw_framebuffer, GetDrawFramebufferTarget(), gl_error, func_name); | |
| Framebuffer* read_framebuffer = GetBoundReadFramebuffer(); | |
| valid = valid && CheckFramebufferValid( | |
| read_framebuffer, GetReadFramebufferTarget(), gl_error, func_name); | |
| return valid; | |
| } | |
| bool GLES2DecoderImpl::FormsTextureCopyingFeedbackLoop( | |
| TextureRef* texture, GLint level, GLint layer) { | |
| Framebuffer* framebuffer = GetBoundReadFramebuffer(); | |
| if (!framebuffer) | |
| return false; | |
| const Framebuffer::Attachment* attachment = | |
| framebuffer->GetReadBufferAttachment(); | |
| if (!attachment) | |
| return false; | |
| return attachment->FormsFeedbackLoop(texture, level, layer); | |
| } | |
| gfx::Size GLES2DecoderImpl::GetBoundReadFramebufferSize() { | |
| Framebuffer* framebuffer = GetBoundReadFramebuffer(); | |
| if (framebuffer) { | |
| return framebuffer->GetFramebufferValidSize(); | |
| } else if (offscreen_target_frame_buffer_.get()) { | |
| return offscreen_size_; | |
| } else { | |
| return surface_->GetSize(); | |
| } | |
| } | |
| gfx::Size GLES2DecoderImpl::GetBoundDrawFramebufferSize() { | |
| Framebuffer* framebuffer = GetBoundDrawFramebuffer(); | |
| if (framebuffer) { | |
| return framebuffer->GetFramebufferValidSize(); | |
| } else if (offscreen_target_frame_buffer_.get()) { | |
| return offscreen_size_; | |
| } else { | |
| return surface_->GetSize(); | |
| } | |
| } | |
| GLuint GLES2DecoderImpl::GetBoundReadFramebufferServiceId() { | |
| Framebuffer* framebuffer = GetBoundReadFramebuffer(); | |
| if (framebuffer) { | |
| return framebuffer->service_id(); | |
| } | |
| if (surface_.get()) { | |
| return surface_->GetBackingFramebufferObject(); | |
| } | |
| return 0; | |
| } | |
| GLuint GLES2DecoderImpl::GetBoundDrawFramebufferServiceId() const { | |
| Framebuffer* framebuffer = GetBoundDrawFramebuffer(); | |
| if (framebuffer) { | |
| return framebuffer->service_id(); | |
| } | |
| if (offscreen_target_frame_buffer_.get()) { | |
| return offscreen_target_frame_buffer_->id(); | |
| } | |
| if (surface_.get()) { | |
| return surface_->GetBackingFramebufferObject(); | |
| } | |
| return 0; | |
| } | |
| GLenum GLES2DecoderImpl::GetBoundReadFramebufferTextureType() { | |
| Framebuffer* framebuffer = GetBoundReadFramebuffer(); | |
| if (framebuffer) { | |
| return framebuffer->GetReadBufferTextureType(); | |
| } else { // Back buffer. | |
| if (back_buffer_read_buffer_ == GL_NONE) | |
| return 0; | |
| return GL_UNSIGNED_BYTE; | |
| } | |
| } | |
| GLenum GLES2DecoderImpl::GetBoundReadFramebufferInternalFormat() { | |
| Framebuffer* framebuffer = GetBoundReadFramebuffer(); | |
| if (framebuffer) { | |
| return framebuffer->GetReadBufferInternalFormat(); | |
| } else { // Back buffer. | |
| if (back_buffer_read_buffer_ == GL_NONE) | |
| return 0; | |
| if (offscreen_target_frame_buffer_.get()) { | |
| return offscreen_target_color_format_; | |
| } | |
| return back_buffer_color_format_; | |
| } | |
| } | |
| bool GLES2DecoderImpl::IsBoundReadFramebufferMultisampledRenderbuffer() { | |
| Framebuffer* read_framebuffer = GetBoundReadFramebuffer(); | |
| // We check if the read framebuffer is a multisampled renderbuffer. | |
| // We explicitly do NOT block multisampled texture attachments (with target | |
| // GL_TEXTURE_2D, etc.) because they are implicit resolve textures (allocated | |
| // via FramebufferTexture2DMultisampleEXT) and are safe to copy from (they are | |
| // resolved on the fly). True multisampled textures | |
| // (GL_TEXTURE_2D_MULTISAMPLE) are not supported by the validating decoder. | |
| return read_framebuffer && | |
| read_framebuffer->GetReadBufferIsMultisampledRenderbuffer(); | |
| } | |
| GLenum GLES2DecoderImpl::GetBoundColorDrawBufferType(GLint drawbuffer_i) { | |
| DCHECK(drawbuffer_i >= 0 && | |
| drawbuffer_i < static_cast<GLint>(group_->max_draw_buffers())); | |
| Framebuffer* framebuffer = GetBoundDrawFramebuffer(); | |
| if (!framebuffer) { | |
| return 0; | |
| } | |
| GLenum drawbuffer = static_cast<GLenum>(GL_DRAW_BUFFER0 + drawbuffer_i); | |
| if (framebuffer->GetDrawBuffer(drawbuffer) == GL_NONE) { | |
| return 0; | |
| } | |
| GLenum attachment = static_cast<GLenum>(GL_COLOR_ATTACHMENT0 + drawbuffer_i); | |
| const Framebuffer::Attachment* buffer = | |
| framebuffer->GetAttachment(attachment); | |
| if (!buffer) { | |
| return 0; | |
| } | |
| return buffer->texture_type(); | |
| } | |
| GLenum GLES2DecoderImpl::GetBoundColorDrawBufferInternalFormat( | |
| GLint drawbuffer_i) { | |
| DCHECK(drawbuffer_i >= 0 && | |
| drawbuffer_i < static_cast<GLint>(group_->max_draw_buffers())); | |
| Framebuffer* framebuffer = GetBoundDrawFramebuffer(); | |
| if (!framebuffer) { | |
| return 0; | |
| } | |
| GLenum drawbuffer = static_cast<GLenum>(GL_DRAW_BUFFER0 + drawbuffer_i); | |
| if (framebuffer->GetDrawBuffer(drawbuffer) == GL_NONE) { | |
| return 0; | |
| } | |
| GLenum attachment = static_cast<GLenum>(GL_COLOR_ATTACHMENT0 + drawbuffer_i); | |
| const Framebuffer::Attachment* buffer = | |
| framebuffer->GetAttachment(attachment); | |
| if (!buffer) { | |
| return 0; | |
| } | |
| return buffer->internal_format(); | |
| } | |
| GLsizei GLES2DecoderImpl::GetBoundFramebufferSamples(GLenum target) { | |
| DCHECK(target == GL_DRAW_FRAMEBUFFER || target == GL_READ_FRAMEBUFFER || | |
| target == GL_FRAMEBUFFER); | |
| Framebuffer* framebuffer = GetFramebufferInfoForTarget(target); | |
| if (framebuffer) { | |
| return framebuffer->GetSamples(); | |
| } else { // Back buffer. | |
| return 0; | |
| } | |
| } | |
| GLenum GLES2DecoderImpl::GetBoundFramebufferDepthFormat( | |
| GLenum target) { | |
| DCHECK(target == GL_DRAW_FRAMEBUFFER || target == GL_READ_FRAMEBUFFER || | |
| target == GL_FRAMEBUFFER); | |
| Framebuffer* framebuffer = GetFramebufferInfoForTarget(target); | |
| if (framebuffer) { | |
| return framebuffer->GetDepthFormat(); | |
| } else { // Back buffer. | |
| if (offscreen_target_frame_buffer_.get()) { | |
| return 0; | |
| } | |
| if (back_buffer_has_depth_) | |
| return GL_DEPTH; | |
| return 0; | |
| } | |
| } | |
| GLenum GLES2DecoderImpl::GetBoundFramebufferStencilFormat( | |
| GLenum target) { | |
| DCHECK(target == GL_DRAW_FRAMEBUFFER || target == GL_READ_FRAMEBUFFER || | |
| target == GL_FRAMEBUFFER); | |
| Framebuffer* framebuffer = GetFramebufferInfoForTarget(target); | |
| if (framebuffer) { | |
| return framebuffer->GetStencilFormat(); | |
| } else { // Back buffer. | |
| if (offscreen_target_frame_buffer_.get()) { | |
| return 0; | |
| } | |
| if (back_buffer_has_stencil_) | |
| return GL_STENCIL; | |
| return 0; | |
| } | |
| } | |
| void GLES2DecoderImpl::MarkDrawBufferAsCleared( | |
| GLenum buffer, GLint drawbuffer_i) { | |
| Framebuffer* framebuffer = GetBoundDrawFramebuffer(); | |
| if (!framebuffer) | |
| return; | |
| GLenum attachment = 0; | |
| switch (buffer) { | |
| case GL_COLOR: | |
| DCHECK(drawbuffer_i >= 0 && | |
| drawbuffer_i < static_cast<GLint>(group_->max_draw_buffers())); | |
| attachment = static_cast<GLenum>(GL_COLOR_ATTACHMENT0 + drawbuffer_i); | |
| break; | |
| case GL_DEPTH: | |
| attachment = GL_DEPTH_ATTACHMENT; | |
| break; | |
| case GL_STENCIL: | |
| attachment = GL_STENCIL_ATTACHMENT; | |
| break; | |
| default: | |
| // Caller is responsible for breaking GL_DEPTH_STENCIL into GL_DEPTH and | |
| // GL_STENCIL. | |
| NOTREACHED(); | |
| } | |
| framebuffer->MarkAttachmentAsCleared( | |
| renderbuffer_manager(), texture_manager(), attachment, true); | |
| } | |
| Logger* GLES2DecoderImpl::GetLogger() { | |
| return &logger_; | |
| } | |
| void GLES2DecoderImpl::BeginDecoding() { | |
| gpu_tracer_->BeginDecoding(); | |
| gpu_trace_commands_ = | |
| gpu_tracer_->IsTracing() && | |
| TRACE_EVENT_CATEGORY_ENABLED(TRACE_DISABLED_BY_DEFAULT("gpu.decoder")); | |
| gpu_debug_commands_ = log_commands() || debug() || gpu_trace_commands_; | |
| query_manager_->ProcessFrameBeginUpdates(); | |
| query_manager_->BeginProcessingCommands(); | |
| } | |
| void GLES2DecoderImpl::EndDecoding() { | |
| gpu_tracer_->EndDecoding(); | |
| query_manager_->EndProcessingCommands(); | |
| } | |
| ErrorState* GLES2DecoderImpl::GetErrorState() { | |
| return error_state_.get(); | |
| } | |
| bool GLES2DecoderImpl::GetServiceTextureId(uint32_t client_texture_id, | |
| uint32_t* service_texture_id) { | |
| TextureRef* texture_ref = texture_manager()->GetTexture(client_texture_id); | |
| if (texture_ref) { | |
| *service_texture_id = texture_ref->service_id(); | |
| return true; | |
| } | |
| return false; | |
| } | |
| TextureBase* GLES2DecoderImpl::GetTextureBase(uint32_t client_id) { | |
| TextureRef* texture_ref = texture_manager()->GetTexture(client_id); | |
| return texture_ref ? texture_ref->texture() : nullptr; | |
| } | |
| void GLES2DecoderImpl::SetLevelInfo(uint32_t client_id, | |
| int level, | |
| unsigned internal_format, | |
| unsigned width, | |
| unsigned height, | |
| unsigned depth, | |
| unsigned format, | |
| unsigned type, | |
| const gfx::Rect& cleared_rect) { | |
| TextureRef* texture_ref = texture_manager()->GetTexture(client_id); | |
| texture_manager()->SetLevelInfo(texture_ref, texture_ref->texture()->target(), | |
| level, internal_format, width, height, depth, | |
| 0 /* border */, format, type, cleared_rect); | |
| } | |
| void GLES2DecoderImpl::BindFramebuffer(unsigned target, | |
| uint32_t service_id) const { | |
| if (workarounds().ensure_previous_framebuffer_not_deleted) { | |
| DCHECK(complete_fbo_); | |
| api()->glBindFramebufferEXTFn(target, complete_fbo_); | |
| } | |
| api()->glBindFramebufferEXTFn(target, service_id); | |
| } | |
| void GLES2DecoderImpl::Destroy(bool have_context) { | |
| if (!initialized()) | |
| return; | |
| DCHECK(!have_context || context_->IsCurrent(nullptr)); | |
| if (have_context) { | |
| if (copy_tex_image_blit_.get()) { | |
| copy_tex_image_blit_->Destroy(); | |
| copy_tex_image_blit_.reset(); | |
| } | |
| if (copy_texture_chromium_.get()) { | |
| copy_texture_chromium_->Destroy(); | |
| copy_texture_chromium_.reset(); | |
| } | |
| if (clear_framebuffer_blit_.get()) { | |
| clear_framebuffer_blit_->Destroy(); | |
| clear_framebuffer_blit_.reset(); | |
| } | |
| if (state_.current_program.get()) { | |
| program_manager()->UnuseProgram(shader_manager(), | |
| state_.current_program.get()); | |
| } | |
| if (attrib_0_buffer_id_) { | |
| api()->glDeleteBuffersARBFn(1, &attrib_0_buffer_id_); | |
| } | |
| if (fixed_attrib_buffer_id_) { | |
| api()->glDeleteBuffersARBFn(1, &fixed_attrib_buffer_id_); | |
| } | |
| if (validation_fbo_) { | |
| api()->glDeleteFramebuffersEXTFn(1, &validation_fbo_multisample_); | |
| api()->glDeleteFramebuffersEXTFn(1, &validation_fbo_); | |
| } | |
| while (!validation_textures_.empty()) { | |
| GLuint tex; | |
| tex = validation_textures_.begin()->second; | |
| api()->glDeleteTexturesFn(1, &tex); | |
| validation_textures_.erase(validation_textures_.begin()); | |
| } | |
| if (offscreen_target_frame_buffer_.get()) | |
| offscreen_target_frame_buffer_->Destroy(); | |
| if (offscreen_target_color_texture_.get()) | |
| offscreen_target_color_texture_->Destroy(); | |
| if (complete_fbo_color_texture_) { | |
| api()->glDeleteTexturesFn(1, &complete_fbo_color_texture_); | |
| } | |
| if (complete_fbo_) { | |
| api()->glDeleteFramebuffersEXTFn(1, &complete_fbo_); | |
| } | |
| } else { | |
| if (offscreen_target_frame_buffer_.get()) | |
| offscreen_target_frame_buffer_->Invalidate(); | |
| if (offscreen_target_color_texture_.get()) | |
| offscreen_target_color_texture_->Invalidate(); | |
| for (auto& fence : deschedule_until_finished_fences_) { | |
| fence->Invalidate(); | |
| } | |
| if (group_ && group_->texture_manager()) | |
| group_->texture_manager()->MarkContextLost(); | |
| state_.MarkContextLost(); | |
| } | |
| deschedule_until_finished_fences_.clear(); | |
| ReportProgress(); | |
| // Unbind everything. | |
| state_.vertex_attrib_manager = nullptr; | |
| state_.default_vertex_attrib_manager = nullptr; | |
| state_.texture_units.clear(); | |
| state_.sampler_units.clear(); | |
| state_.bound_array_buffer = nullptr; | |
| state_.bound_copy_read_buffer = nullptr; | |
| state_.bound_copy_write_buffer = nullptr; | |
| state_.bound_pixel_pack_buffer = nullptr; | |
| state_.bound_pixel_unpack_buffer = nullptr; | |
| state_.bound_transform_feedback_buffer = nullptr; | |
| state_.bound_uniform_buffer = nullptr; | |
| framebuffer_state_.bound_read_framebuffer = nullptr; | |
| framebuffer_state_.bound_draw_framebuffer = nullptr; | |
| state_.current_draw_framebuffer_client_id = 0; | |
| state_.bound_renderbuffer = nullptr; | |
| state_.bound_transform_feedback = nullptr; | |
| state_.default_transform_feedback = nullptr; | |
| state_.indexed_uniform_buffer_bindings = nullptr; | |
| // Current program must be cleared after calling ProgramManager::UnuseProgram. | |
| // Otherwise, we can leak objects. http://crbug.com/258772. | |
| // state_.current_program must be reset before group_ is reset because | |
| // the later deletes the ProgramManager object that referred by | |
| // state_.current_program object. | |
| state_.current_program = nullptr; | |
| copy_tex_image_blit_.reset(); | |
| copy_texture_chromium_.reset(); | |
| clear_framebuffer_blit_.reset(); | |
| ReportProgress(); | |
| if (framebuffer_manager_.get()) { | |
| framebuffer_manager_->Destroy(have_context); | |
| if (group_->texture_manager()) | |
| group_->texture_manager()->RemoveFramebufferManager( | |
| framebuffer_manager_.get()); | |
| framebuffer_manager_.reset(); | |
| } | |
| multi_draw_manager_.reset(); | |
| if (query_manager_.get()) { | |
| query_manager_->Destroy(have_context); | |
| query_manager_.reset(); | |
| } | |
| if (gpu_fence_manager_.get()) { | |
| gpu_fence_manager_->Destroy(have_context); | |
| gpu_fence_manager_.reset(); | |
| } | |
| if (vertex_array_manager_ .get()) { | |
| vertex_array_manager_->Destroy(have_context); | |
| vertex_array_manager_.reset(); | |
| } | |
| if (transform_feedback_manager_.get()) { | |
| if (!have_context) { | |
| transform_feedback_manager_->MarkContextLost(); | |
| } | |
| transform_feedback_manager_->Destroy(); | |
| transform_feedback_manager_.reset(); | |
| } | |
| ReportProgress(); | |
| offscreen_target_frame_buffer_.reset(); | |
| offscreen_target_color_texture_.reset(); | |
| // Release all fences now, because some fence types need the context to be | |
| // current on destruction. | |
| pending_readpixel_fences_ = base::queue<FenceCallback>(); | |
| // Need to release these before releasing |group_| which may own the | |
| // ShaderTranslatorCache. | |
| DestroyShaderTranslator(); | |
| ReportProgress(); | |
| // Destroy the GPU Tracer which may own some in process GPU Timings. | |
| if (gpu_tracer_) { | |
| gpu_tracer_->Destroy(have_context); | |
| gpu_tracer_.reset(); | |
| } | |
| if (group_.get()) { | |
| group_->Destroy(this, have_context); | |
| group_ = nullptr; | |
| } | |
| if (context_.get()) { | |
| context_->ReleaseCurrent(nullptr); | |
| context_ = nullptr; | |
| } | |
| surface_ = nullptr; | |
| } | |
| void GLES2DecoderImpl::SetSurface(const scoped_refptr<gl::GLSurface>& surface) { | |
| DCHECK(context_->IsCurrent(nullptr)); | |
| DCHECK(surface); | |
| surface_ = surface; | |
| RestoreCurrentFramebufferBindings(); | |
| } | |
| void GLES2DecoderImpl::ReleaseSurface() { | |
| if (!context_.get()) | |
| return; | |
| if (WasContextLost()) { | |
| DLOG(ERROR) << " GLES2DecoderImpl: Trying to release lost context."; | |
| return; | |
| } | |
| context_->ReleaseCurrent(surface_.get()); | |
| surface_ = nullptr; | |
| } | |
| error::Error GLES2DecoderImpl::HandleCreateGpuFenceINTERNAL( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::CreateGpuFenceINTERNAL& c = | |
| *static_cast<const volatile gles2::cmds::CreateGpuFenceINTERNAL*>( | |
| cmd_data); | |
| if (!features().chromium_gpu_fence) { | |
| return error::kUnknownCommand; | |
| } | |
| GLuint gpu_fence_id = static_cast<GLuint>(c.gpu_fence_id); | |
| if (!GetGpuFenceManager()->CreateGpuFence(gpu_fence_id)) | |
| return error::kInvalidArguments; | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleWaitGpuFenceCHROMIUM( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::WaitGpuFenceCHROMIUM& c = | |
| *static_cast<const volatile gles2::cmds::WaitGpuFenceCHROMIUM*>(cmd_data); | |
| if (!features().chromium_gpu_fence) { | |
| return error::kUnknownCommand; | |
| } | |
| GLuint gpu_fence_id = static_cast<GLuint>(c.gpu_fence_id); | |
| if (!GetGpuFenceManager()->GpuFenceServerWait(gpu_fence_id)) | |
| return error::kInvalidArguments; | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleDestroyGpuFenceCHROMIUM( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::DestroyGpuFenceCHROMIUM& c = | |
| *static_cast<const volatile gles2::cmds::DestroyGpuFenceCHROMIUM*>( | |
| cmd_data); | |
| if (!features().chromium_gpu_fence) { | |
| return error::kUnknownCommand; | |
| } | |
| GLuint gpu_fence_id = static_cast<GLuint>(c.gpu_fence_id); | |
| if (!GetGpuFenceManager()->RemoveGpuFence(gpu_fence_id)) | |
| return error::kInvalidArguments; | |
| return error::kNoError; | |
| } | |
| bool GLES2DecoderImpl::ResizeOffscreenFramebuffer(const gfx::Size& size) { | |
| bool is_offscreen = !!offscreen_target_frame_buffer_.get(); | |
| if (!is_offscreen) { | |
| LOG(ERROR) << "GLES2DecoderImpl::ResizeOffscreenFramebuffer called " | |
| << " with an onscreen framebuffer."; | |
| return false; | |
| } | |
| if (offscreen_size_ == size) | |
| return true; | |
| offscreen_size_ = size; | |
| int w = offscreen_size_.width(); | |
| int h = offscreen_size_.height(); | |
| if (w < 0 || h < 0 || w > max_offscreen_framebuffer_size_ || | |
| h > max_offscreen_framebuffer_size_) { | |
| LOG(ERROR) << "GLES2DecoderImpl::ResizeOffscreenFramebuffer failed " | |
| << "to allocate storage due to excessive dimensions."; | |
| return false; | |
| } | |
| // Reallocate the offscreen target buffers. | |
| DCHECK(offscreen_target_color_format_); | |
| if (!offscreen_target_color_texture_->AllocateStorage( | |
| offscreen_size_, offscreen_target_color_format_, false)) { | |
| LOG(ERROR) << "GLES2DecoderImpl::ResizeOffscreenFramebuffer failed " | |
| << "to allocate storage for offscreen target color texture."; | |
| return false; | |
| } | |
| // Attach the offscreen target buffers to the target frame buffer. | |
| offscreen_target_frame_buffer_->AttachRenderTexture( | |
| offscreen_target_color_texture_.get()); | |
| if (offscreen_target_frame_buffer_->CheckStatus() != | |
| GL_FRAMEBUFFER_COMPLETE) { | |
| LOG(ERROR) << "GLES2DecoderImpl::ResizeOffscreenFramebuffer failed " | |
| << "because offscreen FBO was incomplete."; | |
| return false; | |
| } | |
| // Clear the target frame buffer. | |
| { | |
| ScopedFramebufferBinder binder(this, offscreen_target_frame_buffer_->id()); | |
| api()->glClearColorFn(0, 0, 0, 1.0f); | |
| state_.SetDeviceColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); | |
| api()->glClearStencilFn(0); | |
| state_.SetDeviceStencilMaskSeparate(GL_FRONT, kDefaultStencilMask); | |
| state_.SetDeviceStencilMaskSeparate(GL_BACK, kDefaultStencilMask); | |
| api()->glClearDepthFn(0); | |
| state_.SetDeviceDepthMask(GL_TRUE); | |
| state_.SetDeviceCapabilityState(GL_SCISSOR_TEST, false); | |
| ClearDeviceWindowRectangles(); | |
| api()->glClearFn(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | | |
| GL_STENCIL_BUFFER_BIT); | |
| RestoreClearState(); | |
| } | |
| return true; | |
| } | |
| const char* GLES2DecoderImpl::GetCommandName(unsigned int command_id) const { | |
| if (command_id >= kFirstGLES2Command && command_id < kNumCommands) { | |
| return gles2::GetCommandName(static_cast<CommandId>(command_id)); | |
| } | |
| return GetCommonCommandName(static_cast<cmd::CommandId>(command_id)); | |
| } | |
| // Decode multiple commands, and call the corresponding GL functions. | |
| // NOTE: 'buffer' is a pointer to the command buffer. As such, it could be | |
| // changed by a (malicious) client at any time, so if validation has to happen, | |
| // it should operate on a copy of them. | |
| // NOTE: This is duplicating code from AsyncAPIInterface::DoCommands() in the | |
| // interest of performance in this critical execution loop. | |
| template <bool DebugImpl> | |
| error::Error GLES2DecoderImpl::DoCommandsImpl(unsigned int num_commands, | |
| const volatile void* buffer, | |
| int num_entries, | |
| int* entries_processed) { | |
| DCHECK(entries_processed); | |
| commands_to_process_ = num_commands; | |
| error::Error result = error::kNoError; | |
| const volatile CommandBufferEntry* cmd_data = | |
| static_cast<const volatile CommandBufferEntry*>(buffer); | |
| int process_pos = 0; | |
| unsigned int command = 0; | |
| while (process_pos < num_entries && result == error::kNoError && | |
| commands_to_process_--) { | |
| const unsigned int size = cmd_data->value_header.size; | |
| command = cmd_data->value_header.command; | |
| if (size == 0) { | |
| result = error::kInvalidSize; | |
| break; | |
| } | |
| if (static_cast<int>(size) + process_pos > num_entries) { | |
| result = error::kOutOfBounds; | |
| break; | |
| } | |
| if (DebugImpl && log_commands()) { | |
| LOG(ERROR) << "[" << logger_.GetLogPrefix() << "]" | |
| << "cmd: " << GetCommandName(command); | |
| } | |
| const unsigned int arg_count = size - 1; | |
| unsigned int command_index = command - kFirstGLES2Command; | |
| if (command_index < command_info.size()) { | |
| const CommandInfo& info = command_info[command_index]; | |
| unsigned int info_arg_count = static_cast<unsigned int>(info.arg_count); | |
| if ((info.arg_flags == cmd::kFixed && arg_count == info_arg_count) || | |
| (info.arg_flags == cmd::kAtLeastN && arg_count >= info_arg_count)) { | |
| bool doing_gpu_trace = false; | |
| if (DebugImpl && gpu_trace_commands_) { | |
| if (CMD_FLAG_GET_TRACE_LEVEL(info.cmd_flags) <= gpu_trace_level_) { | |
| doing_gpu_trace = true; | |
| gpu_tracer_->Begin(TRACE_DISABLED_BY_DEFAULT("gpu.decoder"), | |
| GetCommandName(command), kTraceDecoder); | |
| } | |
| } | |
| uint32_t immediate_data_size = (arg_count - info_arg_count) * | |
| sizeof(CommandBufferEntry); // NOLINT | |
| result = (this->*info.cmd_handler)(immediate_data_size, cmd_data); | |
| if (DebugImpl && doing_gpu_trace) | |
| gpu_tracer_->End(kTraceDecoder); | |
| if (DebugImpl && debug() && !WasContextLost()) { | |
| GLenum error; | |
| while ((error = api()->glGetErrorFn()) != GL_NO_ERROR) { | |
| LOG(ERROR) << "[" << logger_.GetLogPrefix() << "] " | |
| << "GL ERROR: " << GLES2Util::GetStringEnum(error) | |
| << " : " << GetCommandName(command); | |
| LOCAL_SET_GL_ERROR(error, "DoCommand", "GL error from driver"); | |
| } | |
| } | |
| } else { | |
| result = error::kInvalidArguments; | |
| } | |
| } else { | |
| result = DoCommonCommand(command, arg_count, cmd_data); | |
| } | |
| if (result == error::kNoError && | |
| current_decoder_error_ != error::kNoError) { | |
| result = current_decoder_error_; | |
| current_decoder_error_ = error::kNoError; | |
| } | |
| if (result != error::kDeferCommandUntilLater) { | |
| process_pos += size; | |
| UNSAFE_TODO(cmd_data += size); | |
| } | |
| } | |
| *entries_processed = process_pos; | |
| if (error::IsError(result)) { | |
| LOG(ERROR) << "Error: " << result << " for Command " | |
| << GetCommandName(command); | |
| } | |
| return result; | |
| } | |
| error::Error GLES2DecoderImpl::DoCommands(unsigned int num_commands, | |
| const volatile void* buffer, | |
| int num_entries, | |
| int* entries_processed) { | |
| if (gpu_debug_commands_) { | |
| return DoCommandsImpl<true>( | |
| num_commands, buffer, num_entries, entries_processed); | |
| } else { | |
| return DoCommandsImpl<false>( | |
| num_commands, buffer, num_entries, entries_processed); | |
| } | |
| } | |
| void GLES2DecoderImpl::ExitCommandProcessingEarly() { | |
| commands_to_process_ = 0; | |
| } | |
| void GLES2DecoderImpl::DoFinish() { | |
| api()->glFinishFn(); | |
| ProcessPendingReadPixels(true); | |
| ProcessPendingQueries(true); | |
| ApplyRecreateFboWorkaroundIfNeeded(); | |
| } | |
| void GLES2DecoderImpl::DoFlush() { | |
| api()->glFlushFn(); | |
| ProcessPendingQueries(false); | |
| ApplyRecreateFboWorkaroundIfNeeded(); | |
| } | |
| void GLES2DecoderImpl::DoActiveTexture(GLenum texture_unit) { | |
| GLuint texture_index = texture_unit - GL_TEXTURE0; | |
| if (texture_index >= state_.texture_units.size()) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM( | |
| "glActiveTexture", texture_unit, "texture_unit"); | |
| return; | |
| } | |
| state_.active_texture_unit = texture_index; | |
| api()->glActiveTextureFn(texture_unit); | |
| } | |
| void GLES2DecoderImpl::DoBindBuffer(GLenum target, GLuint client_id) { | |
| Buffer* buffer = nullptr; | |
| GLuint service_id = 0; | |
| if (client_id != 0) { | |
| buffer = GetBuffer(client_id); | |
| if (!buffer) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glBindBuffer", | |
| "id not generated by glGenBuffers"); | |
| return; | |
| } | |
| } | |
| LogClientServiceForInfo(buffer, client_id, "glBindBuffer"); | |
| if (buffer) { | |
| if (!buffer_manager()->SetTarget(buffer, target)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, | |
| "glBindBuffer", "buffer bound to more than 1 target"); | |
| return; | |
| } | |
| service_id = buffer->service_id(); | |
| } | |
| state_.SetBoundBuffer(target, buffer); | |
| api()->glBindBufferFn(target, service_id); | |
| } | |
| void GLES2DecoderImpl::BindIndexedBufferImpl( | |
| GLenum target, GLuint index, GLuint client_id, | |
| GLintptr offset, GLsizeiptr size, | |
| BindIndexedBufferFunctionType function_type, const char* function_name) { | |
| switch (target) { | |
| case GL_TRANSFORM_FEEDBACK_BUFFER: { | |
| if (index >= group_->max_transform_feedback_separate_attribs()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, | |
| "index out of range"); | |
| return; | |
| } | |
| DCHECK(state_.bound_transform_feedback.get()); | |
| if (state_.bound_transform_feedback->active()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "bound transform feedback is active"); | |
| return; | |
| } | |
| break; | |
| } | |
| case GL_UNIFORM_BUFFER: { | |
| if (index >= group_->max_uniform_buffer_bindings()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, | |
| "index out of range"); | |
| return; | |
| } | |
| break; | |
| } | |
| default: | |
| NOTREACHED(); | |
| } | |
| if (function_type == BindIndexedBufferFunctionType::kBindBufferRange) { | |
| switch (target) { | |
| case GL_TRANSFORM_FEEDBACK_BUFFER: | |
| if ((size % 4 != 0) || (offset % 4 != 0)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, | |
| "size or offset are not multiples of 4"); | |
| return; | |
| } | |
| break; | |
| case GL_UNIFORM_BUFFER: { | |
| if (offset % group_->uniform_buffer_offset_alignment() != 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, | |
| "offset is not a multiple of UNIFORM_BUFFER_OFFSET_ALIGNMENT"); | |
| return; | |
| } | |
| break; | |
| } | |
| default: | |
| NOTREACHED(); | |
| } | |
| if (client_id != 0) { | |
| if (size <= 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "size <= 0"); | |
| return; | |
| } | |
| if (offset < 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "offset < 0"); | |
| return; | |
| } | |
| } | |
| } | |
| Buffer* buffer = nullptr; | |
| if (client_id != 0) { | |
| buffer = GetBuffer(client_id); | |
| if (!buffer) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "id not generated by glGenBuffers"); | |
| return; | |
| } | |
| if (!buffer_manager()->SetTarget(buffer, target)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "buffer bound to more than 1 target"); | |
| return; | |
| } | |
| } | |
| LogClientServiceForInfo(buffer, client_id, function_name); | |
| scoped_refptr<IndexedBufferBindingHost> bindings; | |
| switch (target) { | |
| case GL_TRANSFORM_FEEDBACK_BUFFER: | |
| bindings = state_.bound_transform_feedback.get(); | |
| break; | |
| case GL_UNIFORM_BUFFER: | |
| bindings = state_.indexed_uniform_buffer_bindings.get(); | |
| break; | |
| default: | |
| NOTREACHED(); | |
| } | |
| DCHECK(bindings); | |
| switch (function_type) { | |
| case BindIndexedBufferFunctionType::kBindBufferBase: | |
| bindings->DoBindBufferBase(index, buffer); | |
| break; | |
| case BindIndexedBufferFunctionType::kBindBufferRange: | |
| bindings->DoBindBufferRange(index, buffer, offset, size); | |
| break; | |
| default: | |
| NOTREACHED(); | |
| } | |
| state_.SetBoundBuffer(target, buffer); | |
| } | |
| void GLES2DecoderImpl::DoBindBufferBase(GLenum target, GLuint index, | |
| GLuint client_id) { | |
| BindIndexedBufferImpl(target, index, client_id, 0, 0, | |
| BindIndexedBufferFunctionType::kBindBufferBase, | |
| "glBindBufferBase"); | |
| } | |
| void GLES2DecoderImpl::DoBindBufferRange(GLenum target, GLuint index, | |
| GLuint client_id, | |
| GLintptr offset, | |
| GLsizeiptr size) { | |
| BindIndexedBufferImpl(target, index, client_id, offset, size, | |
| BindIndexedBufferFunctionType::kBindBufferRange, | |
| "glBindBufferRange"); | |
| } | |
| bool GLES2DecoderImpl::BoundFramebufferAllowsChangesToAlphaChannel() { | |
| Framebuffer* framebuffer = GetBoundDrawFramebuffer(); | |
| if (framebuffer) | |
| return framebuffer->HasAlphaMRT(); | |
| if (back_buffer_draw_buffer_ == GL_NONE) | |
| return false; | |
| if (offscreen_target_frame_buffer_.get()) { | |
| return false; | |
| } | |
| return (back_buffer_color_format_ == GL_RGBA || | |
| back_buffer_color_format_ == GL_RGBA8); | |
| } | |
| bool GLES2DecoderImpl::BoundFramebufferHasDepthAttachment() { | |
| Framebuffer* framebuffer = GetBoundDrawFramebuffer(); | |
| if (framebuffer) { | |
| return framebuffer->HasDepthAttachment(); | |
| } | |
| if (offscreen_target_frame_buffer_.get()) { | |
| return false; | |
| } | |
| return back_buffer_has_depth_; | |
| } | |
| bool GLES2DecoderImpl::BoundFramebufferHasStencilAttachment() { | |
| Framebuffer* framebuffer = GetBoundDrawFramebuffer(); | |
| if (framebuffer) { | |
| return framebuffer->HasStencilAttachment(); | |
| } | |
| if (offscreen_target_frame_buffer_.get()) { | |
| return false; | |
| } | |
| return back_buffer_has_stencil_; | |
| } | |
| void GLES2DecoderImpl::ApplyDirtyState() { | |
| if (framebuffer_state_.clear_state_dirty) { | |
| bool allows_alpha_change = BoundFramebufferAllowsChangesToAlphaChannel(); | |
| state_.SetDeviceColorMask(state_.color_mask_red, state_.color_mask_green, | |
| state_.color_mask_blue, | |
| state_.color_mask_alpha && allows_alpha_change); | |
| bool have_depth = BoundFramebufferHasDepthAttachment(); | |
| state_.SetDeviceDepthMask(state_.depth_mask && have_depth); | |
| bool have_stencil = BoundFramebufferHasStencilAttachment(); | |
| state_.SetDeviceStencilMaskSeparate( | |
| GL_FRONT, have_stencil ? state_.stencil_front_writemask : 0); | |
| state_.SetDeviceStencilMaskSeparate( | |
| GL_BACK, have_stencil ? state_.stencil_back_writemask : 0); | |
| state_.SetDeviceCapabilityState( | |
| GL_DEPTH_TEST, state_.enable_flags.depth_test && have_depth); | |
| state_.SetDeviceCapabilityState( | |
| GL_STENCIL_TEST, state_.enable_flags.stencil_test && have_stencil); | |
| framebuffer_state_.clear_state_dirty = false; | |
| } | |
| } | |
| GLuint GLES2DecoderImpl::GetBackbufferServiceId() const { | |
| return (offscreen_target_frame_buffer_.get()) | |
| ? offscreen_target_frame_buffer_->id() | |
| : (surface_.get() ? surface_->GetBackingFramebufferObject() : 0); | |
| } | |
| void GLES2DecoderImpl::RestoreState(const ContextState* prev_state) { | |
| TRACE_EVENT1("gpu", "GLES2DecoderImpl::RestoreState", | |
| "context", logger_.GetLogPrefix()); | |
| // Restore the Framebuffer first because of bugs in Intel drivers. | |
| // Intel drivers incorrectly clip the viewport settings to | |
| // the size of the current framebuffer object. | |
| RestoreFramebufferBindings(); | |
| state_.RestoreState(prev_state); | |
| } | |
| void GLES2DecoderImpl::RestoreBufferBinding(unsigned int target) { | |
| if (target == GL_PIXEL_PACK_BUFFER) { | |
| state_.UpdatePackParameters(); | |
| } else if (target == GL_PIXEL_UNPACK_BUFFER) { | |
| state_.UpdateUnpackParameters(); | |
| } | |
| Buffer* bound_buffer = | |
| buffer_manager()->GetBufferInfoForTarget(&state_, target); | |
| api()->glBindBufferFn(target, bound_buffer ? bound_buffer->service_id() : 0); | |
| } | |
| void GLES2DecoderImpl::RestoreFramebufferBindings() const { | |
| GLuint service_id = | |
| framebuffer_state_.bound_draw_framebuffer.get() | |
| ? framebuffer_state_.bound_draw_framebuffer->service_id() | |
| : GetBackbufferServiceId(); | |
| if (!SupportsSeparateFramebufferBinds()) { | |
| BindFramebuffer(GL_FRAMEBUFFER, service_id); | |
| } else { | |
| BindFramebuffer(GL_DRAW_FRAMEBUFFER, service_id); | |
| service_id = framebuffer_state_.bound_read_framebuffer.get() | |
| ? framebuffer_state_.bound_read_framebuffer->service_id() | |
| : GetBackbufferServiceId(); | |
| BindFramebuffer(GL_READ_FRAMEBUFFER, service_id); | |
| } | |
| OnFboChanged(); | |
| } | |
| void GLES2DecoderImpl::RestoreRenderbufferBindings() { | |
| state_.RestoreRenderbufferBindings(); | |
| } | |
| void GLES2DecoderImpl::RestoreTextureState(unsigned service_id) { | |
| Texture* texture = texture_manager()->GetTextureForServiceId(service_id); | |
| if (texture) { | |
| GLenum target = texture->target(); | |
| api()->glBindTextureFn(target, service_id); | |
| api()->glTexParameteriFn(target, GL_TEXTURE_WRAP_S, texture->wrap_s()); | |
| api()->glTexParameteriFn(target, GL_TEXTURE_WRAP_T, texture->wrap_t()); | |
| api()->glTexParameteriFn(target, GL_TEXTURE_MIN_FILTER, | |
| texture->min_filter()); | |
| api()->glTexParameteriFn(target, GL_TEXTURE_MAG_FILTER, | |
| texture->mag_filter()); | |
| if (feature_info_->IsWebGL2OrES3Context()) { | |
| api()->glTexParameteriFn(target, GL_TEXTURE_BASE_LEVEL, | |
| texture->base_level()); | |
| } | |
| RestoreTextureUnitBindings(state_.active_texture_unit); | |
| } | |
| } | |
| void GLES2DecoderImpl::ClearDeviceWindowRectangles() const { | |
| if (!feature_info_->feature_flags().ext_window_rectangles) { | |
| return; | |
| } | |
| api()->glWindowRectanglesEXTFn(GL_EXCLUSIVE_EXT, 0, nullptr); | |
| } | |
| void GLES2DecoderImpl::RestoreDeviceWindowRectangles() const { | |
| state_.UpdateWindowRectangles(); | |
| } | |
| void GLES2DecoderImpl::ClearAllAttributes() const { | |
| // Must use native VAO 0, as RestoreAllAttributes can't fully restore | |
| // other VAOs. | |
| if (feature_info_->feature_flags().native_vertex_array_object) | |
| api()->glBindVertexArrayOESFn(0); | |
| for (uint32_t i = 0; i < group_->max_vertex_attribs(); ++i) { | |
| if (i != 0) // Never disable attribute 0 | |
| state_.vertex_attrib_manager->SetDriverVertexAttribEnabled(i, false); | |
| if (features().angle_instanced_arrays) | |
| api()->glVertexAttribDivisorANGLEFn(i, 0); | |
| } | |
| } | |
| void GLES2DecoderImpl::RestoreAllAttributes() const { | |
| state_.RestoreVertexAttribs(nullptr); | |
| } | |
| void GLES2DecoderImpl::SetIgnoreCachedStateForTest(bool ignore) { | |
| state_.SetIgnoreCachedStateForTest(ignore); | |
| } | |
| void GLES2DecoderImpl::SetForceShaderNameHashingForTest(bool force) { | |
| force_shader_name_hashing_for_test = force; | |
| } | |
| // Added specifically for testing backbuffer_needs_clear_bits unittests. | |
| uint32_t GLES2DecoderImpl::GetAndClearBackbufferClearBitsForTest() { | |
| uint32_t clear_bits = backbuffer_needs_clear_bits_; | |
| backbuffer_needs_clear_bits_ = 0; | |
| return clear_bits; | |
| } | |
| void GLES2DecoderImpl::ApplyRecreateFboWorkaroundIfNeeded() const { | |
| if (!workarounds().recreate_fbo_upon_flush) { | |
| return; | |
| } | |
| Framebuffer* draw_fbo = framebuffer_state_.bound_draw_framebuffer.get(); | |
| if (!draw_fbo || draw_fbo->service_id() == 0) { | |
| return; | |
| } | |
| bool need_workaround = false; | |
| const GLenum attachments[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT}; | |
| for (GLenum attachment_point : attachments) { | |
| const Framebuffer::Attachment* attachment = | |
| draw_fbo->GetAttachment(attachment_point); | |
| if (!attachment) { | |
| continue; | |
| } | |
| if (attachment->IsRenderbufferAttachment()) { | |
| need_workaround = true; | |
| break; | |
| } else if (attachment->IsTextureAttachment()) { | |
| GLuint client_id = attachment->object_name(); | |
| const TextureRef* texture_ref = texture_manager()->GetTexture(client_id); | |
| if (texture_ref) { | |
| const Texture* texture = texture_ref->texture(); | |
| if (texture->HasImmutableStorage()) { | |
| GLenum target = attachment->target(); | |
| GLint level = attachment->level(); | |
| if (!texture->IsLevelCleared(target, level)) { | |
| need_workaround = true; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if (need_workaround) { | |
| framebuffer_manager_.get()->RecreateFramebufferServiceId(draw_fbo); | |
| if (framebuffer_state_.bound_read_framebuffer.get() == draw_fbo) { | |
| api()->glBindFramebufferEXTFn(GL_FRAMEBUFFER, draw_fbo->service_id()); | |
| draw_fbo->ReattachAttachments(GL_FRAMEBUFFER); | |
| api()->glReadBufferFn(draw_fbo->read_buffer()); | |
| } else { | |
| api()->glBindFramebufferEXTFn(GL_DRAW_FRAMEBUFFER, | |
| draw_fbo->service_id()); | |
| draw_fbo->ReattachAttachments(GL_DRAW_FRAMEBUFFER); | |
| } | |
| } | |
| } | |
| void GLES2DecoderImpl::OnFboChanged() const { | |
| state_.fbo_binding_for_scissor_workaround_dirty = true; | |
| state_.stencil_state_changed_since_validation = true; | |
| if (workarounds().flush_on_framebuffer_change) { | |
| api()->glFlushFn(); | |
| ApplyRecreateFboWorkaroundIfNeeded(); | |
| } | |
| } | |
| // Called after the FBO is checked for completeness. | |
| void GLES2DecoderImpl::OnUseFramebuffer() const { | |
| if (!state_.fbo_binding_for_scissor_workaround_dirty) | |
| return; | |
| state_.fbo_binding_for_scissor_workaround_dirty = false; | |
| if (workarounds().force_update_scissor_state_when_binding_fbo0 && | |
| GetBoundDrawFramebufferServiceId() == 0) { | |
| // The theory is that FBO0 keeps some internal (in HW regs maybe?) scissor | |
| // test state, but the driver forgets to update it with GL_SCISSOR_TEST | |
| // when FBO0 gets bound. (So it stuck with whatever state we last switched | |
| // from it.) | |
| // If the internal scissor test state was enabled, it does update its | |
| // internal scissor rect with GL_SCISSOR_BOX though. | |
| if (state_.enable_flags.cached_scissor_test) { | |
| // The driver early outs if the new state matches previous state so some | |
| // shake up is needed. | |
| api()->glDisableFn(GL_SCISSOR_TEST); | |
| api()->glEnableFn(GL_SCISSOR_TEST); | |
| } else { | |
| // Ditto. | |
| api()->glEnableFn(GL_SCISSOR_TEST); | |
| api()->glDisableFn(GL_SCISSOR_TEST); | |
| } | |
| } | |
| } | |
| void GLES2DecoderImpl::DoBindFramebuffer(GLenum target, GLuint client_id) { | |
| Framebuffer* framebuffer = nullptr; | |
| GLuint service_id = 0; | |
| if (client_id != 0) { | |
| framebuffer = GetFramebuffer(client_id); | |
| if (!framebuffer) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glBindFramebuffer", | |
| "id not generated by glGenFramebuffers"); | |
| return; | |
| } else { | |
| service_id = framebuffer->service_id(); | |
| } | |
| framebuffer->MarkAsValid(); | |
| } | |
| LogClientServiceForInfo(framebuffer, client_id, "glBindFramebuffer"); | |
| if (target == GL_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER) { | |
| framebuffer_state_.bound_draw_framebuffer = framebuffer; | |
| state_.UpdateWindowRectanglesForBoundDrawFramebufferClientID(client_id); | |
| } | |
| // vmiura: This looks like dup code | |
| if (target == GL_FRAMEBUFFER || target == GL_READ_FRAMEBUFFER) { | |
| framebuffer_state_.bound_read_framebuffer = framebuffer; | |
| } | |
| framebuffer_state_.clear_state_dirty = true; | |
| // If we are rendering to the backbuffer get the FBO id for any simulated | |
| // backbuffer. | |
| if (framebuffer == nullptr) { | |
| service_id = GetBackbufferServiceId(); | |
| } | |
| FlushQueriesBeforeDeletingOrUnbindingFboWorkaround(); | |
| BindFramebuffer(target, service_id); | |
| OnFboChanged(); | |
| } | |
| void GLES2DecoderImpl::DoBindRenderbuffer(GLenum target, GLuint client_id) { | |
| DCHECK_EQ(target, (GLenum)GL_RENDERBUFFER); | |
| Renderbuffer* renderbuffer = nullptr; | |
| GLuint service_id = 0; | |
| if (client_id != 0) { | |
| renderbuffer = GetRenderbuffer(client_id); | |
| if (!renderbuffer) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glBindRenderbuffer", | |
| "id not generated by glGenRenderbuffers"); | |
| return; | |
| } else { | |
| service_id = renderbuffer->service_id(); | |
| } | |
| renderbuffer->MarkAsValid(); | |
| } | |
| LogClientServiceForInfo(renderbuffer, client_id, "glBindRenderbuffer"); | |
| state_.bound_renderbuffer = renderbuffer; | |
| state_.bound_renderbuffer_valid = true; | |
| api()->glBindRenderbufferEXTFn(GL_RENDERBUFFER, service_id); | |
| } | |
| void GLES2DecoderImpl::UpdateTextureBinding(GLenum target, | |
| GLuint client_id, | |
| TextureRef* texture) { | |
| CHECK(texture); | |
| size_t last_active_unit_index = state_.active_texture_unit; | |
| for (size_t curr_unit_index = 0; | |
| curr_unit_index < state_.texture_units.size(); curr_unit_index++) { | |
| auto curr_unit = state_.texture_units[curr_unit_index]; | |
| auto* curr_texture = curr_unit.GetInfoForTarget(target); | |
| if (!curr_texture) { | |
| continue; | |
| } | |
| if (curr_texture->client_id() != client_id) { | |
| continue; | |
| } | |
| // `target` is bound to `client_id` in this unit, so we need to update the | |
| // service-side binding. | |
| // First update the active texture unit if needed. | |
| if (last_active_unit_index != curr_unit_index) { | |
| api()->glActiveTextureFn( | |
| static_cast<GLenum>(GL_TEXTURE0 + curr_unit_index)); | |
| last_active_unit_index = curr_unit_index; | |
| } | |
| // Now update the texture binding. | |
| api()->glBindTextureFn(target, texture->service_id()); | |
| curr_unit.SetInfoForTarget(target, texture); | |
| } | |
| // Reset the active texture unit if it was changed. | |
| if (last_active_unit_index != state_.active_texture_unit) { | |
| api()->glActiveTextureFn( | |
| static_cast<GLenum>(GL_TEXTURE0 + state_.active_texture_unit)); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoBindTexture(GLenum target, GLuint client_id) { | |
| TextureRef* texture_ref = nullptr; | |
| if (client_id != 0) { | |
| texture_ref = GetTexture(client_id); | |
| if (!texture_ref) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glBindTexture", | |
| "id not generated by glGenTextures"); | |
| return; | |
| } | |
| } else { | |
| texture_ref = texture_manager()->GetDefaultTextureInfo(target); | |
| } | |
| // Check the texture exists | |
| if (texture_ref) { | |
| Texture* texture = texture_ref->texture(); | |
| // Check that we are not trying to bind it to a different target. | |
| if (texture->target() != 0 && texture->target() != target) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, | |
| "glBindTexture", | |
| "texture bound to more than 1 target."); | |
| return; | |
| } | |
| LogClientServiceForInfo(texture, client_id, "glBindTexture"); | |
| api()->glBindTextureFn(target, texture->service_id()); | |
| if (texture->target() == 0) { | |
| texture_manager()->SetTarget(texture_ref, target); | |
| } | |
| } else { | |
| api()->glBindTextureFn(target, 0); | |
| } | |
| TextureUnit& unit = state_.texture_units[state_.active_texture_unit]; | |
| unit.bind_target = target; | |
| unit.SetInfoForTarget(target, texture_ref); | |
| } | |
| void GLES2DecoderImpl::DoBindSampler(GLuint unit, GLuint client_id) { | |
| if (unit >= group_->max_texture_units()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glBindSampler", "unit out of bounds"); | |
| return; | |
| } | |
| Sampler* sampler = nullptr; | |
| if (client_id != 0) { | |
| sampler = GetSampler(client_id); | |
| if (!sampler) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glBindSampler", | |
| "id not generated by glGenSamplers"); | |
| return; | |
| } | |
| } | |
| // Check the sampler exists | |
| if (sampler) { | |
| LogClientServiceForInfo(sampler, client_id, "glBindSampler"); | |
| api()->glBindSamplerFn(unit, sampler->service_id()); | |
| } else { | |
| api()->glBindSamplerFn(unit, 0); | |
| } | |
| state_.sampler_units[unit] = sampler; | |
| } | |
| void GLES2DecoderImpl::DoBindTransformFeedback( | |
| GLenum target, GLuint client_id) { | |
| const char* function_name = "glBindTransformFeedback"; | |
| TransformFeedback* transform_feedback = nullptr; | |
| if (client_id != 0) { | |
| transform_feedback = GetTransformFeedback(client_id); | |
| if (!transform_feedback) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "id not generated by glGenTransformFeedbacks"); | |
| return; | |
| } | |
| } else { | |
| transform_feedback = state_.default_transform_feedback.get(); | |
| } | |
| DCHECK(transform_feedback); | |
| if (transform_feedback == state_.bound_transform_feedback.get()) | |
| return; | |
| if (state_.bound_transform_feedback->active() && | |
| !state_.bound_transform_feedback->paused()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "currently bound transform feedback is active"); | |
| return; | |
| } | |
| LogClientServiceForInfo(transform_feedback, client_id, function_name); | |
| transform_feedback->DoBindTransformFeedback( | |
| target, state_.bound_transform_feedback.get(), | |
| state_.bound_transform_feedback_buffer.get()); | |
| state_.bound_transform_feedback = transform_feedback; | |
| } | |
| void GLES2DecoderImpl::DoBeginTransformFeedback(GLenum primitive_mode) { | |
| const char* function_name = "glBeginTransformFeedback"; | |
| TransformFeedback* transform_feedback = state_.bound_transform_feedback.get(); | |
| DCHECK(transform_feedback); | |
| if (transform_feedback->active()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "transform feedback is already active"); | |
| return; | |
| } | |
| if (!CheckCurrentProgram(function_name)) { | |
| return; | |
| } | |
| Program* program = state_.current_program.get(); | |
| DCHECK(program); | |
| size_t required_buffer_count = | |
| program->effective_transform_feedback_varyings().size(); | |
| if (required_buffer_count == 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "no active transform feedback varyings"); | |
| return; | |
| } | |
| if (required_buffer_count > 1 && | |
| GL_INTERLEAVED_ATTRIBS == | |
| program->effective_transform_feedback_buffer_mode()) { | |
| required_buffer_count = 1; | |
| } | |
| for (size_t ii = 0; ii < required_buffer_count; ++ii) { | |
| Buffer* buffer = transform_feedback->GetBufferBinding(ii); | |
| if (!buffer) { | |
| std::string msg = base::StringPrintf("missing buffer bound at index %i", | |
| static_cast<int>(ii)); | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, msg.c_str()); | |
| return; | |
| } | |
| if (buffer->IsDoubleBoundForTransformFeedback()) { | |
| std::string msg = base::StringPrintf( | |
| "buffer at index %i is bound for multiple transform feedback outputs", | |
| static_cast<int>(ii)); | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, msg.c_str()); | |
| return; | |
| } | |
| } | |
| transform_feedback->DoBeginTransformFeedback(primitive_mode); | |
| DCHECK(transform_feedback->active()); | |
| transform_feedback->SetActiveProgram(program); | |
| } | |
| void GLES2DecoderImpl::DoEndTransformFeedback() { | |
| const char* function_name = "glEndTransformFeedback"; | |
| DCHECK(state_.bound_transform_feedback.get()); | |
| if (!state_.bound_transform_feedback->active()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "transform feedback is not active"); | |
| return; | |
| } | |
| // TODO(zmo): Validate binding points. | |
| state_.bound_transform_feedback->DoEndTransformFeedback(); | |
| Program* program = state_.bound_transform_feedback->active_program(); | |
| if (program) { | |
| state_.bound_transform_feedback->ClearActiveProgram(); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoPauseTransformFeedback() { | |
| DCHECK(state_.bound_transform_feedback.get()); | |
| if (!state_.bound_transform_feedback->active() || | |
| state_.bound_transform_feedback->paused()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glPauseTransformFeedback", | |
| "transform feedback is not active or already paused"); | |
| return; | |
| } | |
| state_.bound_transform_feedback->DoPauseTransformFeedback(); | |
| } | |
| void GLES2DecoderImpl::DoResumeTransformFeedback() { | |
| DCHECK(state_.bound_transform_feedback.get()); | |
| if (!state_.bound_transform_feedback->active() || | |
| !state_.bound_transform_feedback->paused()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glResumeTransformFeedback", | |
| "transform feedback is not active or not paused"); | |
| return; | |
| } | |
| if (state_.current_program.get() != | |
| state_.bound_transform_feedback->active_program()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glResumeTransformFeedback", | |
| "current program does not match the program active when " | |
| "transform feedback began"); | |
| return; | |
| } | |
| state_.bound_transform_feedback->DoResumeTransformFeedback(); | |
| } | |
| void GLES2DecoderImpl::DoDisableVertexAttribArray(GLuint index) { | |
| if (state_.vertex_attrib_manager->Enable(index, false)) { | |
| state_.vertex_attrib_manager->SetDriverVertexAttribEnabled(index, false); | |
| } else { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, | |
| "glDisableVertexAttribArray", "index out of range"); | |
| } | |
| } | |
| void GLES2DecoderImpl::InvalidateFramebufferImpl( | |
| GLenum target, | |
| GLsizei count, | |
| const volatile GLenum* attachments, | |
| GLint x, | |
| GLint y, | |
| GLsizei width, | |
| GLsizei height, | |
| const char* function_name, | |
| FramebufferOperation op) { | |
| Framebuffer* framebuffer = GetFramebufferInfoForTarget(target); | |
| // Because of performance issues, no-op if the format of the attachment is | |
| // DEPTH_STENCIL and only one part is intended to be invalidated. | |
| bool has_depth_stencil_format = framebuffer && | |
| framebuffer->HasDepthStencilFormatAttachment(); | |
| bool invalidate_depth = false; | |
| bool invalidate_stencil = false; | |
| auto validated_attachments = base::HeapArray<GLenum>::Uninit(count + 1); | |
| GLsizei validated_count = 0; | |
| // Validates the attachments. If one of them fails, the whole command fails. | |
| GLenum thresh0 = GL_COLOR_ATTACHMENT0 + group_->max_color_attachments(); | |
| GLenum thresh1 = GL_COLOR_ATTACHMENT15; | |
| for (GLsizei i = 0; i < count; ++i) { | |
| GLenum attachment = UNSAFE_TODO(attachments[i]); | |
| if (framebuffer) { | |
| if (attachment >= thresh0 && attachment <= thresh1) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, function_name, "invalid attachment"); | |
| return; | |
| } | |
| if (!validators_->attachment.IsValid(attachment)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM(function_name, attachment, | |
| "attachments"); | |
| return; | |
| } | |
| if (has_depth_stencil_format) { | |
| switch (attachment) { | |
| case GL_DEPTH_ATTACHMENT: | |
| invalidate_depth = true; | |
| continue; | |
| case GL_STENCIL_ATTACHMENT: | |
| invalidate_stencil = true; | |
| continue; | |
| case GL_DEPTH_STENCIL_ATTACHMENT: | |
| invalidate_depth = true; | |
| invalidate_stencil = true; | |
| continue; | |
| } | |
| } | |
| } else { | |
| if (!validators_->backbuffer_attachment.IsValid(attachment)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM(function_name, attachment, | |
| "attachments"); | |
| return; | |
| } | |
| } | |
| validated_attachments[validated_count++] = attachment; | |
| } | |
| if (invalidate_depth && invalidate_stencil) { | |
| // We do not use GL_DEPTH_STENCIL_ATTACHMENT here because | |
| // it is not a valid token for glDiscardFramebufferEXT. | |
| validated_attachments[validated_count++] = GL_DEPTH_ATTACHMENT; | |
| validated_attachments[validated_count++] = GL_STENCIL_ATTACHMENT; | |
| } | |
| // If the default framebuffer is bound but we are still rendering to an | |
| // FBO, translate attachment names that refer to default framebuffer | |
| // channels to corresponding framebuffer attachments. | |
| auto translated_attachments = | |
| base::HeapArray<GLenum>::Uninit(validated_count); | |
| for (GLsizei i = 0; i < validated_count; ++i) { | |
| GLenum attachment = validated_attachments[i]; | |
| if (!framebuffer && GetBackbufferServiceId()) { | |
| switch (attachment) { | |
| case GL_COLOR_EXT: | |
| attachment = GL_COLOR_ATTACHMENT0; | |
| break; | |
| case GL_DEPTH_EXT: | |
| attachment = GL_DEPTH_ATTACHMENT; | |
| break; | |
| case GL_STENCIL_EXT: | |
| attachment = GL_STENCIL_ATTACHMENT; | |
| break; | |
| default: | |
| NOTREACHED(); | |
| } | |
| } | |
| translated_attachments[i] = attachment; | |
| } | |
| bool skip_api_call = false; | |
| if (!framebuffer) { | |
| // Don't invalidate backbuffer. | |
| skip_api_call = true; | |
| } | |
| if (workarounds().dont_invalidate_incomplete_fbos) { | |
| if (DoCheckFramebufferStatus(target) != GL_FRAMEBUFFER_COMPLETE) { | |
| skip_api_call = true; | |
| } | |
| } | |
| bool dirty = false; | |
| switch (op) { | |
| case kFramebufferDiscard: | |
| if (!skip_api_call) { | |
| if (gl_version_info().is_es3) { | |
| api()->glInvalidateFramebufferFn(target, validated_count, | |
| translated_attachments.data()); | |
| } else { | |
| api()->glDiscardFramebufferEXTFn(target, validated_count, | |
| translated_attachments.data()); | |
| } | |
| } | |
| dirty = true; | |
| break; | |
| case kFramebufferInvalidate: | |
| if (!skip_api_call) { | |
| api()->glInvalidateFramebufferFn(target, validated_count, | |
| translated_attachments.data()); | |
| } | |
| dirty = true; | |
| break; | |
| case kFramebufferInvalidateSub: | |
| // Make it an no-op because we don't have a mechanism to mark partial | |
| // pixels uncleared yet. | |
| // TODO(zmo): Revisit this. | |
| break; | |
| } | |
| if (!dirty) | |
| return; | |
| // Marks each one of them as not cleared. | |
| for (GLsizei i = 0; i < validated_count; ++i) { | |
| if (framebuffer) { | |
| if (validated_attachments[i] == GL_DEPTH_STENCIL_ATTACHMENT) { | |
| framebuffer->MarkAttachmentAsCleared(renderbuffer_manager(), | |
| texture_manager(), | |
| GL_DEPTH_ATTACHMENT, | |
| false); | |
| framebuffer->MarkAttachmentAsCleared(renderbuffer_manager(), | |
| texture_manager(), | |
| GL_STENCIL_ATTACHMENT, | |
| false); | |
| } else { | |
| framebuffer->MarkAttachmentAsCleared(renderbuffer_manager(), | |
| texture_manager(), | |
| validated_attachments[i], | |
| false); | |
| } | |
| } else { | |
| switch (validated_attachments[i]) { | |
| case GL_COLOR_EXT: | |
| backbuffer_needs_clear_bits_ |= GL_COLOR_BUFFER_BIT; | |
| break; | |
| case GL_DEPTH_EXT: | |
| backbuffer_needs_clear_bits_ |= GL_DEPTH_BUFFER_BIT; | |
| break; | |
| case GL_STENCIL_EXT: | |
| backbuffer_needs_clear_bits_ |= GL_STENCIL_BUFFER_BIT; | |
| break; | |
| default: | |
| NOTREACHED(); | |
| } | |
| } | |
| } | |
| } | |
| void GLES2DecoderImpl::DoDiscardFramebufferEXT( | |
| GLenum target, | |
| GLsizei count, | |
| const volatile GLenum* attachments) { | |
| if (workarounds().disable_discard_framebuffer) | |
| return; | |
| const GLsizei kWidthNotUsed = 1; | |
| const GLsizei kHeightNotUsed = 1; | |
| InvalidateFramebufferImpl( | |
| target, count, attachments, 0, 0, kWidthNotUsed, kHeightNotUsed, | |
| "glDiscardFramebufferEXT", kFramebufferDiscard); | |
| } | |
| void GLES2DecoderImpl::DoInvalidateFramebuffer( | |
| GLenum target, | |
| GLsizei count, | |
| const volatile GLenum* attachments) { | |
| const GLsizei kWidthNotUsed = 1; | |
| const GLsizei kHeightNotUsed = 1; | |
| InvalidateFramebufferImpl( | |
| target, count, attachments, 0, 0, kWidthNotUsed, kHeightNotUsed, | |
| "glInvalidateFramebuffer", kFramebufferInvalidate); | |
| } | |
| void GLES2DecoderImpl::DoInvalidateSubFramebuffer( | |
| GLenum target, | |
| GLsizei count, | |
| const volatile GLenum* attachments, | |
| GLint x, | |
| GLint y, | |
| GLsizei width, | |
| GLsizei height) { | |
| InvalidateFramebufferImpl( | |
| target, count, attachments, x, y, width, height, | |
| "glInvalidateSubFramebuffer", kFramebufferInvalidateSub); | |
| } | |
| void GLES2DecoderImpl::DoEnableVertexAttribArray(GLuint index) { | |
| if (state_.vertex_attrib_manager->Enable(index, true)) { | |
| state_.vertex_attrib_manager->SetDriverVertexAttribEnabled(index, true); | |
| } else { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glEnableVertexAttribArray", "index out of range"); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoGenerateMipmap(GLenum target) { | |
| TextureRef* texture_ref = texture_manager()->GetTextureInfoForTarget( | |
| &state_, target); | |
| if (!texture_ref || | |
| !texture_manager()->CanGenerateMipmaps(texture_ref)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, "glGenerateMipmap", "Can not generate mips"); | |
| return; | |
| } | |
| Texture* tex = texture_ref->texture(); | |
| GLint base_level = tex->base_level(); | |
| if (target == GL_TEXTURE_CUBE_MAP) { | |
| for (int i = 0; i < 6; ++i) { | |
| GLenum face = GL_TEXTURE_CUBE_MAP_POSITIVE_X + i; | |
| if (!texture_manager()->ClearTextureLevel(this, texture_ref, face, | |
| base_level)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_OUT_OF_MEMORY, "glGenerateMipmap", "dimensions too big"); | |
| return; | |
| } | |
| } | |
| } else { | |
| if (!texture_manager()->ClearTextureLevel(this, texture_ref, target, | |
| base_level)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_OUT_OF_MEMORY, "glGenerateMipmap", "dimensions too big"); | |
| return; | |
| } | |
| } | |
| if (workarounds().recreate_mipmap_levels_before_generate && | |
| !tex->IsImmutable()) { | |
| RecreateMipmapLevelsBeforeGenerate(texture_ref, target); | |
| } | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER("glGenerateMipmap"); | |
| api()->glGenerateMipmapEXTFn(target); | |
| GLenum error = LOCAL_PEEK_GL_ERROR("glGenerateMipmap"); | |
| if (error == GL_NO_ERROR) { | |
| texture_manager()->MarkMipmapsGenerated(texture_ref); | |
| } | |
| } | |
| void GLES2DecoderImpl::RecreateMipmapLevelsBeforeGenerate( | |
| TextureRef* texture_ref, | |
| GLenum target) { | |
| DCHECK(texture_ref); | |
| Texture* tex = texture_ref->texture(); | |
| DCHECK(tex); | |
| DCHECK(!tex->IsImmutable()); | |
| const GLint base_level = tex->base_level(); | |
| const size_t num_faces = (target == GL_TEXTURE_CUBE_MAP) ? 6 : 1; | |
| ScopedPixelUnpackState reset_restore(&state_); | |
| for (size_t face_idx = 0; face_idx < num_faces; ++face_idx) { | |
| const GLenum face_target = (target == GL_TEXTURE_CUBE_MAP) | |
| ? GLES2Util::IndexToGLFaceTarget(face_idx) | |
| : target; | |
| const Texture::LevelInfo* base_info = | |
| tex->GetLevelInfo(face_target, base_level); | |
| if (!base_info) { | |
| continue; | |
| } | |
| const GLenum internal_format = base_info->internal_format; | |
| const GLenum format = base_info->format; | |
| const GLenum type = base_info->type; | |
| const GLenum adjusted_internal_format = | |
| TextureManager::AdjustTexInternalFormat(feature_info_.get(), | |
| internal_format, type); | |
| const GLenum adjusted_format = | |
| TextureManager::AdjustTexFormat(feature_info_.get(), format); | |
| const GLsizei num_mips = tex->NumMipLevels(face_idx); | |
| GLsizei level_width = base_info->width; | |
| GLsizei level_height = base_info->height; | |
| GLsizei level_depth = base_info->depth; | |
| for (GLsizei level = base_level + 1; level < base_level + num_mips; | |
| ++level) { | |
| level_width = std::max(1, level_width >> 1); | |
| level_height = std::max(1, level_height >> 1); | |
| if (target != GL_TEXTURE_2D_ARRAY) { | |
| level_depth = std::max(1, level_depth >> 1); | |
| } | |
| const Texture::LevelInfo* level_info = | |
| tex->GetLevelInfo(face_target, level); | |
| const bool needs_recreate = | |
| !level_info || level_info->width != level_width || | |
| level_info->height != level_height || | |
| level_info->depth != level_depth || | |
| level_info->internal_format != internal_format || | |
| level_info->type != type; | |
| if (needs_recreate) { | |
| if (face_target == GL_TEXTURE_3D || | |
| face_target == GL_TEXTURE_2D_ARRAY) { | |
| api()->glTexImage3DFn(face_target, level, adjusted_internal_format, | |
| level_width, level_height, level_depth, 0, | |
| adjusted_format, type, nullptr); | |
| } else { | |
| api()->glTexImage2DFn(face_target, level, adjusted_internal_format, | |
| level_width, level_height, 0, adjusted_format, | |
| type, nullptr); | |
| } | |
| texture_manager()->SetLevelInfo( | |
| texture_ref, face_target, level, internal_format, level_width, | |
| level_height, level_depth, 0, format, type, gfx::Rect()); | |
| } | |
| } | |
| } | |
| } | |
| bool GLES2DecoderImpl::GetHelper( | |
| GLenum pname, GLint* params, GLsizei* num_written) { | |
| DCHECK(num_written); | |
| switch (pname) { | |
| case GL_IMPLEMENTATION_COLOR_READ_FORMAT: | |
| case GL_IMPLEMENTATION_COLOR_READ_TYPE: | |
| *num_written = 1; | |
| { | |
| Framebuffer* framebuffer = GetBoundReadFramebuffer(); | |
| if (framebuffer && | |
| framebuffer->IsPossiblyComplete(feature_info_.get()) != | |
| GL_FRAMEBUFFER_COMPLETE) { | |
| // Here we avoid querying the driver framebuffer status because the | |
| // above should cover most cases. This is an effort to reduce crashes | |
| // on MacOSX. See crbug.com/662802. | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, "glGetIntegerv", "incomplete framebuffer"); | |
| if (params) { | |
| *params = 0; | |
| } | |
| return true; | |
| } | |
| } | |
| if (params) { | |
| api()->glGetIntegervFn(pname, params); | |
| if (*params == GL_HALF_FLOAT && feature_info_->IsWebGL1OrES2Context()) { | |
| *params = GL_HALF_FLOAT_OES; | |
| } | |
| if (*params == GL_SRGB_ALPHA_EXT) { | |
| *params = GL_RGBA; | |
| } | |
| if (*params == GL_SRGB_EXT) { | |
| *params = GL_RGB; | |
| } | |
| } | |
| return true; | |
| default: | |
| break; | |
| } | |
| if (feature_info_->IsWebGL2OrES3Context()) { | |
| switch (pname) { | |
| case GL_MAX_VARYING_COMPONENTS: | |
| // We can just delegate this query to the driver. | |
| *num_written = 1; | |
| break; | |
| case GL_READ_BUFFER: | |
| *num_written = 1; | |
| if (params) { | |
| Framebuffer* framebuffer = GetBoundReadFramebuffer(); | |
| GLenum read_buffer; | |
| if (framebuffer) { | |
| read_buffer = framebuffer->read_buffer(); | |
| } else { | |
| read_buffer = back_buffer_read_buffer_; | |
| } | |
| *params = static_cast<GLint>(read_buffer); | |
| } | |
| return true; | |
| case GL_TRANSFORM_FEEDBACK_ACTIVE: | |
| *num_written = 1; | |
| if (params) { | |
| *params = | |
| static_cast<GLint>(state_.bound_transform_feedback->active()); | |
| } | |
| return true; | |
| case GL_TRANSFORM_FEEDBACK_PAUSED: | |
| *num_written = 1; | |
| if (params) { | |
| *params = | |
| static_cast<GLint>(state_.bound_transform_feedback->paused()); | |
| } | |
| return true; | |
| case GL_WINDOW_RECTANGLE_EXT: | |
| *num_written = 4; | |
| // This is only used for glGetIntegeri_v and similar, so params will | |
| // always be null - the only path here is through | |
| // GetNumValuesReturnedForGLGet. | |
| DCHECK(!params); | |
| return true; | |
| } | |
| } | |
| switch (pname) { | |
| case GL_MAX_VIEWPORT_DIMS: | |
| *num_written = 2; | |
| if (offscreen_target_frame_buffer_.get()) { | |
| if (params) { | |
| params[0] = renderbuffer_manager()->max_renderbuffer_size(); | |
| UNSAFE_TODO(params[1]) = | |
| renderbuffer_manager()->max_renderbuffer_size(); | |
| } | |
| return true; | |
| } | |
| break; | |
| case GL_MAX_SAMPLES: | |
| *num_written = 1; | |
| if (params) { | |
| params[0] = renderbuffer_manager()->max_samples(); | |
| } | |
| return true; | |
| case GL_MAX_RENDERBUFFER_SIZE: | |
| *num_written = 1; | |
| if (params) { | |
| params[0] = renderbuffer_manager()->max_renderbuffer_size(); | |
| } | |
| return true; | |
| case GL_MAX_TEXTURE_SIZE: | |
| *num_written = 1; | |
| if (params) { | |
| params[0] = texture_manager()->MaxSizeForTarget(GL_TEXTURE_2D); | |
| } | |
| return true; | |
| case GL_MAX_CUBE_MAP_TEXTURE_SIZE: | |
| *num_written = 1; | |
| if (params) { | |
| params[0] = texture_manager()->MaxSizeForTarget(GL_TEXTURE_CUBE_MAP); | |
| } | |
| return true; | |
| case GL_MAX_3D_TEXTURE_SIZE: | |
| *num_written = 1; | |
| if (params) { | |
| params[0] = texture_manager()->MaxSizeForTarget(GL_TEXTURE_3D); | |
| } | |
| return true; | |
| case GL_MAX_ARRAY_TEXTURE_LAYERS: | |
| *num_written = 1; | |
| if (params) { | |
| params[0] = texture_manager()->max_array_texture_layers(); | |
| } | |
| return true; | |
| case GL_MAX_COLOR_ATTACHMENTS_EXT: | |
| *num_written = 1; | |
| if (params) { | |
| params[0] = group_->max_color_attachments(); | |
| } | |
| return true; | |
| case GL_MAX_DRAW_BUFFERS: | |
| *num_written = 1; | |
| if (params) { | |
| params[0] = group_->max_draw_buffers(); | |
| } | |
| return true; | |
| case GL_ALPHA_BITS: | |
| *num_written = 1; | |
| if (params) { | |
| GLint v = 0; | |
| Framebuffer* framebuffer = GetBoundDrawFramebuffer(); | |
| if (framebuffer) { | |
| if (framebuffer->HasAlphaMRT() && | |
| framebuffer->HasSameInternalFormatsMRT()) { | |
| api()->glGetIntegervFn(GL_ALPHA_BITS, &v); | |
| } | |
| } else { | |
| v = (ClientExposedBackBufferHasAlpha() ? 8 : 0); | |
| } | |
| params[0] = v; | |
| } | |
| return true; | |
| case GL_DEPTH_BITS: | |
| *num_written = 1; | |
| if (params) { | |
| GLint v = 0; | |
| api()->glGetIntegervFn(GL_DEPTH_BITS, &v); | |
| params[0] = BoundFramebufferHasDepthAttachment() ? v : 0; | |
| } | |
| return true; | |
| case GL_RED_BITS: | |
| case GL_GREEN_BITS: | |
| case GL_BLUE_BITS: | |
| *num_written = 1; | |
| if (params) { | |
| GLint v = 0; | |
| api()->glGetIntegervFn(pname, &v); | |
| params[0] = v; | |
| } | |
| return true; | |
| case GL_STENCIL_BITS: | |
| *num_written = 1; | |
| if (params) { | |
| GLint v = 0; | |
| api()->glGetIntegervFn(GL_STENCIL_BITS, &v); | |
| params[0] = BoundFramebufferHasStencilAttachment() ? v : 0; | |
| } | |
| return true; | |
| case GL_COMPRESSED_TEXTURE_FORMATS: | |
| *num_written = validators_->compressed_texture_format.GetValues().size(); | |
| if (params) { | |
| for (GLint ii = 0; ii < *num_written; ++ii) { | |
| UNSAFE_TODO(params[ii]) = | |
| validators_->compressed_texture_format.GetValues()[ii]; | |
| } | |
| } | |
| return true; | |
| case GL_NUM_COMPRESSED_TEXTURE_FORMATS: | |
| *num_written = 1; | |
| if (params) { | |
| *params = validators_->compressed_texture_format.GetValues().size(); | |
| } | |
| return true; | |
| case GL_NUM_SHADER_BINARY_FORMATS: | |
| *num_written = 1; | |
| if (params) { | |
| *params = validators_->shader_binary_format.GetValues().size(); | |
| } | |
| return true; | |
| case GL_SHADER_BINARY_FORMATS: | |
| *num_written = validators_->shader_binary_format.GetValues().size(); | |
| if (params) { | |
| for (GLint ii = 0; ii < *num_written; ++ii) { | |
| UNSAFE_TODO(params[ii]) = | |
| validators_->shader_binary_format.GetValues()[ii]; | |
| } | |
| } | |
| return true; | |
| case GL_SHADER_COMPILER: | |
| *num_written = 1; | |
| if (params) { | |
| *params = GL_TRUE; | |
| } | |
| return true; | |
| case GL_ARRAY_BUFFER_BINDING: | |
| *num_written = 1; | |
| if (params) { | |
| *params = GetClientId( | |
| buffer_manager(), state_.bound_array_buffer.get()); | |
| } | |
| return true; | |
| case GL_ELEMENT_ARRAY_BUFFER_BINDING: | |
| *num_written = 1; | |
| if (params) { | |
| *params = GetClientId( | |
| buffer_manager(), | |
| state_.vertex_attrib_manager->element_array_buffer()); | |
| } | |
| return true; | |
| case GL_COPY_READ_BUFFER_BINDING: | |
| *num_written = 1; | |
| if (params) { | |
| *params = GetClientId( | |
| buffer_manager(), state_.bound_copy_read_buffer.get()); | |
| } | |
| return true; | |
| case GL_COPY_WRITE_BUFFER_BINDING: | |
| *num_written = 1; | |
| if (params) { | |
| *params = GetClientId( | |
| buffer_manager(), state_.bound_copy_write_buffer.get()); | |
| } | |
| return true; | |
| case GL_PIXEL_PACK_BUFFER_BINDING: | |
| *num_written = 1; | |
| if (params) { | |
| *params = GetClientId( | |
| buffer_manager(), state_.bound_pixel_pack_buffer.get()); | |
| } | |
| return true; | |
| case GL_PIXEL_UNPACK_BUFFER_BINDING: | |
| *num_written = 1; | |
| if (params) { | |
| *params = GetClientId( | |
| buffer_manager(), state_.bound_pixel_unpack_buffer.get()); | |
| } | |
| return true; | |
| case GL_TRANSFORM_FEEDBACK_BUFFER_BINDING: | |
| *num_written = 1; | |
| if (params) { | |
| *params = GetClientId( | |
| buffer_manager(), state_.bound_transform_feedback_buffer.get()); | |
| } | |
| return true; | |
| case GL_UNIFORM_BUFFER_BINDING: | |
| *num_written = 1; | |
| if (params) { | |
| *params = GetClientId( | |
| buffer_manager(), state_.bound_uniform_buffer.get()); | |
| } | |
| return true; | |
| case GL_FRAMEBUFFER_BINDING: | |
| // case GL_DRAW_FRAMEBUFFER_BINDING_EXT: (same as GL_FRAMEBUFFER_BINDING) | |
| *num_written = 1; | |
| if (params) { | |
| *params = GetClientId( | |
| framebuffer_manager(), | |
| GetFramebufferInfoForTarget(GL_FRAMEBUFFER)); | |
| } | |
| return true; | |
| case GL_READ_FRAMEBUFFER_BINDING: | |
| *num_written = 1; | |
| if (params) { | |
| *params = GetClientId(framebuffer_manager(), | |
| GetFramebufferInfoForTarget(GL_READ_FRAMEBUFFER)); | |
| } | |
| return true; | |
| case GL_RENDERBUFFER_BINDING: | |
| *num_written = 1; | |
| if (params) { | |
| Renderbuffer* renderbuffer = | |
| GetRenderbufferInfoForTarget(GL_RENDERBUFFER); | |
| if (renderbuffer) { | |
| *params = renderbuffer->client_id(); | |
| } else { | |
| *params = 0; | |
| } | |
| } | |
| return true; | |
| case GL_CURRENT_PROGRAM: | |
| *num_written = 1; | |
| if (params) { | |
| *params = GetClientId(program_manager(), state_.current_program.get()); | |
| } | |
| return true; | |
| case GL_VERTEX_ARRAY_BINDING_OES: | |
| *num_written = 1; | |
| if (params) { | |
| if (state_.vertex_attrib_manager.get() != | |
| state_.default_vertex_attrib_manager.get()) { | |
| GLuint client_id = 0; | |
| vertex_array_manager_->GetClientId( | |
| state_.vertex_attrib_manager->service_id(), &client_id); | |
| *params = client_id; | |
| } else { | |
| *params = 0; | |
| } | |
| } | |
| return true; | |
| case GL_TEXTURE_BINDING_2D: | |
| *num_written = 1; | |
| if (params) { | |
| TextureUnit& unit = state_.texture_units[state_.active_texture_unit]; | |
| if (unit.bound_texture_2d.get()) { | |
| *params = unit.bound_texture_2d->client_id(); | |
| } else { | |
| *params = 0; | |
| } | |
| } | |
| return true; | |
| case GL_TEXTURE_BINDING_CUBE_MAP: | |
| *num_written = 1; | |
| if (params) { | |
| TextureUnit& unit = state_.texture_units[state_.active_texture_unit]; | |
| if (unit.bound_texture_cube_map.get()) { | |
| *params = unit.bound_texture_cube_map->client_id(); | |
| } else { | |
| *params = 0; | |
| } | |
| } | |
| return true; | |
| case GL_TEXTURE_BINDING_EXTERNAL_OES: | |
| *num_written = 1; | |
| if (params) { | |
| TextureUnit& unit = state_.texture_units[state_.active_texture_unit]; | |
| if (unit.bound_texture_external_oes.get()) { | |
| *params = unit.bound_texture_external_oes->client_id(); | |
| } else { | |
| *params = 0; | |
| } | |
| } | |
| return true; | |
| case GL_TEXTURE_BINDING_RECTANGLE_ANGLE: | |
| *num_written = 1; | |
| if (params) { | |
| TextureUnit& unit = state_.texture_units[state_.active_texture_unit]; | |
| if (unit.bound_texture_rectangle_arb.get()) { | |
| *params = unit.bound_texture_rectangle_arb->client_id(); | |
| } else { | |
| *params = 0; | |
| } | |
| } | |
| return true; | |
| case GL_MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT: | |
| *num_written = 1; | |
| if (params) { | |
| params[0] = group_->max_dual_source_draw_buffers(); | |
| } | |
| return true; | |
| case GL_MAJOR_VERSION: | |
| *num_written = 1; | |
| if (params) { | |
| // TODO(zmo): once we switch to MANGLE, we should query version numbers. | |
| params[0] = 3; | |
| } | |
| return true; | |
| case GL_MINOR_VERSION: | |
| // TODO(zmo): once we switch to MANGLE, we should query version numbers. | |
| *num_written = 1; | |
| if (params) { | |
| params[0] = 0; | |
| } | |
| return true; | |
| case GL_NUM_EXTENSIONS: | |
| // TODO(vmiura): Should the command buffer support this? | |
| *num_written = 1; | |
| if (params) { | |
| params[0] = 0; | |
| } | |
| return true; | |
| case GL_GPU_DISJOINT_EXT: | |
| // TODO(vmiura): Should the command buffer support this? | |
| *num_written = 1; | |
| if (params) { | |
| params[0] = 0; | |
| } | |
| return true; | |
| case GL_TIMESTAMP_EXT: | |
| // TODO(vmiura): Should the command buffer support this? | |
| *num_written = 1; | |
| if (params) { | |
| params[0] = 0; | |
| } | |
| return true; | |
| case GL_TEXTURE_BINDING_2D_ARRAY: | |
| *num_written = 1; | |
| if (params) { | |
| TextureUnit& unit = state_.texture_units[state_.active_texture_unit]; | |
| if (unit.bound_texture_2d_array.get()) { | |
| *params = unit.bound_texture_2d_array->client_id(); | |
| } else { | |
| *params = 0; | |
| } | |
| } | |
| return true; | |
| case GL_TEXTURE_BINDING_3D: | |
| *num_written = 1; | |
| if (params) { | |
| TextureUnit& unit = state_.texture_units[state_.active_texture_unit]; | |
| if (unit.bound_texture_3d.get()) { | |
| *params = unit.bound_texture_3d->client_id(); | |
| } else { | |
| *params = 0; | |
| } | |
| } | |
| return true; | |
| case GL_SAMPLER_BINDING: | |
| *num_written = 1; | |
| if (params) { | |
| DCHECK_LT(state_.active_texture_unit, state_.sampler_units.size()); | |
| Sampler* sampler = | |
| state_.sampler_units[state_.active_texture_unit].get(); | |
| *params = sampler ? sampler->client_id() : 0; | |
| #if DCHECK_IS_ON() | |
| if (sampler) { | |
| GLint bound_sampler = 0; | |
| glGetIntegerv(GL_SAMPLER_BINDING, &bound_sampler); | |
| DCHECK_EQ(static_cast<GLuint>(bound_sampler), sampler->service_id()); | |
| } | |
| #endif | |
| } | |
| return true; | |
| case GL_TRANSFORM_FEEDBACK_BINDING: | |
| *num_written = 1; | |
| if (params) { | |
| *params = state_.bound_transform_feedback->client_id(); | |
| } | |
| return true; | |
| case GL_NUM_PROGRAM_BINARY_FORMATS: | |
| *num_written = 1; | |
| if (params) { | |
| *params = 0; | |
| } | |
| return true; | |
| case GL_PROGRAM_BINARY_FORMATS: | |
| *num_written = 0; | |
| return true; | |
| default: | |
| if (pname >= GL_DRAW_BUFFER0 && pname <= GL_DRAW_BUFFER15) { | |
| *num_written = 1; | |
| if (params) { | |
| if (pname < GL_DRAW_BUFFER0 + group_->max_draw_buffers()) { | |
| Framebuffer* framebuffer = | |
| GetFramebufferInfoForTarget(GL_FRAMEBUFFER); | |
| if (framebuffer) { | |
| *params = framebuffer->GetDrawBuffer(pname); | |
| } else { // backbuffer | |
| if (pname == GL_DRAW_BUFFER0) { | |
| *params = back_buffer_draw_buffer_; | |
| } else { | |
| *params = GL_NONE; | |
| } | |
| } | |
| } else { | |
| *params = GL_NONE; | |
| } | |
| } | |
| return true; | |
| } | |
| *num_written = util_.GLGetNumValuesReturned(pname); | |
| if (*num_written) | |
| break; | |
| return false; | |
| } | |
| if (params) { | |
| DCHECK(*num_written); | |
| pname = AdjustGetPname(pname); | |
| api()->glGetIntegervFn(pname, params); | |
| } | |
| return true; | |
| } | |
| bool GLES2DecoderImpl::GetNumValuesReturnedForGLGet( | |
| GLenum pname, GLsizei* num_values) { | |
| *num_values = 0; | |
| if (state_.GetStateAsGLint(pname, nullptr, num_values)) { | |
| return true; | |
| } | |
| return GetHelper(pname, nullptr, num_values); | |
| } | |
| GLenum GLES2DecoderImpl::AdjustGetPname(GLenum pname) { | |
| if (GL_MAX_SAMPLES == pname && | |
| features().use_img_for_multisampled_render_to_texture) { | |
| return GL_MAX_SAMPLES_IMG; | |
| } | |
| return pname; | |
| } | |
| void GLES2DecoderImpl::DoGetBooleanv(GLenum pname, | |
| GLboolean* params, | |
| GLsizei params_size) { | |
| DCHECK(params); | |
| auto values = base::HeapArray<GLint>::WithSize(params_size); | |
| DoGetIntegerv(pname, values.data(), params_size); | |
| for (GLsizei ii = 0; ii < params_size; ++ii) { | |
| UNSAFE_TODO(params[ii]) = static_cast<GLboolean>(values[ii]); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoGetFloatv(GLenum pname, | |
| GLfloat* params, | |
| GLsizei params_size) { | |
| DCHECK(params); | |
| GLsizei num_written = 0; | |
| if (state_.GetStateAsGLfloat(pname, params, &num_written)) { | |
| DCHECK_EQ(num_written, params_size); | |
| return; | |
| } | |
| switch (pname) { | |
| case GL_ALIASED_POINT_SIZE_RANGE: | |
| case GL_ALIASED_LINE_WIDTH_RANGE: | |
| case GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT: | |
| DCHECK_EQ(params_size, util_.GLGetNumValuesReturned(pname)); | |
| pname = AdjustGetPname(pname); | |
| api()->glGetFloatvFn(pname, params); | |
| return; | |
| } | |
| auto values = base::HeapArray<GLint>::WithSize(params_size); | |
| DoGetIntegerv(pname, values.data(), params_size); | |
| for (GLsizei ii = 0; ii < params_size; ++ii) { | |
| UNSAFE_TODO(params[ii]) = static_cast<GLfloat>(values[ii]); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoGetInteger64v(GLenum pname, | |
| GLint64* params, | |
| GLsizei params_size) { | |
| DCHECK(params); | |
| if (feature_info_->IsWebGL2OrES3Context()) { | |
| switch (pname) { | |
| case GL_MAX_ELEMENT_INDEX: { | |
| DCHECK_EQ(params_size, 1); | |
| if (gl_version_info().IsAtLeastGLES(3, 0)) { | |
| api()->glGetInteger64vFn(GL_MAX_ELEMENT_INDEX, params); | |
| } else { | |
| // Assume that desktop GL implementations can generally support | |
| // 32-bit indices. | |
| if (params) { | |
| *params = std::numeric_limits<unsigned int>::max(); | |
| } | |
| } | |
| return; | |
| } | |
| } | |
| } | |
| auto values = base::HeapArray<GLint>::WithSize(params_size); | |
| DoGetIntegerv(pname, values.data(), params_size); | |
| for (GLsizei ii = 0; ii < params_size; ++ii) { | |
| UNSAFE_TODO(params[ii]) = static_cast<GLint64>(values[ii]); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoGetIntegerv(GLenum pname, | |
| GLint* params, | |
| GLsizei params_size) { | |
| GLsizei num_written = 0; | |
| if (params_size == 0) { | |
| return; | |
| } | |
| DCHECK(params); | |
| if (state_.GetStateAsGLint(pname, params, &num_written) || | |
| GetHelper(pname, params, &num_written)) { | |
| DCHECK_EQ(num_written, params_size); | |
| return; | |
| } | |
| NOTREACHED() << "Unhandled enum " << pname; | |
| } | |
| template <typename TYPE> | |
| void GLES2DecoderImpl::GetIndexedIntegerImpl( | |
| const char* function_name, GLenum target, GLuint index, TYPE* data) { | |
| DCHECK(data); | |
| if (features().ext_window_rectangles && target == GL_WINDOW_RECTANGLE_EXT) { | |
| if (index >= state_.GetMaxWindowRectangles()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, | |
| "window rectangle index out of bounds"); | |
| } | |
| // This must be queried from the state tracker because the driver state is | |
| // "wrong" if the bound framebuffer is 0 (backbuffer). | |
| state_.GetWindowRectangle(index, data); | |
| return; | |
| } | |
| scoped_refptr<IndexedBufferBindingHost> bindings; | |
| switch (target) { | |
| case GL_TRANSFORM_FEEDBACK_BUFFER_BINDING: | |
| case GL_TRANSFORM_FEEDBACK_BUFFER_SIZE: | |
| case GL_TRANSFORM_FEEDBACK_BUFFER_START: | |
| if (index >= group_->max_transform_feedback_separate_attribs()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "invalid index"); | |
| return; | |
| } | |
| bindings = state_.bound_transform_feedback.get(); | |
| break; | |
| case GL_UNIFORM_BUFFER_BINDING: | |
| case GL_UNIFORM_BUFFER_SIZE: | |
| case GL_UNIFORM_BUFFER_START: | |
| if (index >= group_->max_uniform_buffer_bindings()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "invalid index"); | |
| return; | |
| } | |
| bindings = state_.indexed_uniform_buffer_bindings.get(); | |
| break; | |
| case GL_BLEND_SRC_RGB: | |
| case GL_BLEND_SRC_ALPHA: | |
| case GL_BLEND_DST_RGB: | |
| case GL_BLEND_DST_ALPHA: | |
| case GL_BLEND_EQUATION_RGB: | |
| case GL_BLEND_EQUATION_ALPHA: | |
| case GL_COLOR_WRITEMASK: | |
| // Note (crbug.com/1058744): not implemented for validating command | |
| // decoder | |
| break; | |
| default: | |
| NOTREACHED(); | |
| } | |
| DCHECK(bindings); | |
| switch (target) { | |
| case GL_TRANSFORM_FEEDBACK_BUFFER_BINDING: | |
| case GL_UNIFORM_BUFFER_BINDING: | |
| { | |
| Buffer* buffer = bindings->GetBufferBinding(index); | |
| *data = static_cast<TYPE>(buffer ? buffer->service_id() : 0); | |
| } | |
| break; | |
| case GL_TRANSFORM_FEEDBACK_BUFFER_SIZE: | |
| case GL_UNIFORM_BUFFER_SIZE: | |
| *data = static_cast<TYPE>(bindings->GetBufferSize(index)); | |
| break; | |
| case GL_TRANSFORM_FEEDBACK_BUFFER_START: | |
| case GL_UNIFORM_BUFFER_START: | |
| *data = static_cast<TYPE>(bindings->GetBufferStart(index)); | |
| break; | |
| case GL_BLEND_SRC_RGB: | |
| case GL_BLEND_SRC_ALPHA: | |
| case GL_BLEND_DST_RGB: | |
| case GL_BLEND_DST_ALPHA: | |
| case GL_BLEND_EQUATION_RGB: | |
| case GL_BLEND_EQUATION_ALPHA: | |
| case GL_COLOR_WRITEMASK: | |
| // Note (crbug.com/1058744): not implemented for validating command | |
| // decoder | |
| break; | |
| default: | |
| NOTREACHED(); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoGetBooleani_v(GLenum target, | |
| GLuint index, | |
| GLboolean* params, | |
| GLsizei params_size) { | |
| GetIndexedIntegerImpl<GLboolean>("glGetBooleani_v", target, index, params); | |
| } | |
| void GLES2DecoderImpl::DoGetIntegeri_v(GLenum target, | |
| GLuint index, | |
| GLint* params, | |
| GLsizei params_size) { | |
| GetIndexedIntegerImpl<GLint>("glGetIntegeri_v", target, index, params); | |
| } | |
| void GLES2DecoderImpl::DoGetInteger64i_v(GLenum target, | |
| GLuint index, | |
| GLint64* params, | |
| GLsizei params_size) { | |
| GetIndexedIntegerImpl<GLint64>("glGetInteger64i_v", target, index, params); | |
| } | |
| void GLES2DecoderImpl::DoGetProgramiv(GLuint program_id, | |
| GLenum pname, | |
| GLint* params, | |
| GLsizei params_size) { | |
| Program* program = GetProgramInfoNotShader(program_id, "glGetProgramiv"); | |
| if (!program) { | |
| return; | |
| } | |
| program->GetProgramiv(pname, params); | |
| } | |
| void GLES2DecoderImpl::DoGetSynciv(GLuint sync_id, | |
| GLenum pname, | |
| GLsizei num_values, | |
| GLsizei* length, | |
| GLint* values) { | |
| GLsync service_sync = 0; | |
| if (!group_->GetSyncServiceId(sync_id, &service_sync)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glGetSynciv", "invalid sync id"); | |
| return; | |
| } | |
| api()->glGetSyncivFn(service_sync, pname, num_values, nullptr, values); | |
| } | |
| void GLES2DecoderImpl::DoGetBufferParameteri64v(GLenum target, | |
| GLenum pname, | |
| GLint64* params, | |
| GLsizei params_size) { | |
| // Just delegate it. Some validation is actually done before this. | |
| buffer_manager()->ValidateAndDoGetBufferParameteri64v( | |
| &state_, error_state_.get(), target, pname, params); | |
| } | |
| void GLES2DecoderImpl::DoGetBufferParameteriv(GLenum target, | |
| GLenum pname, | |
| GLint* params, | |
| GLsizei params_size) { | |
| // Just delegate it. Some validation is actually done before this. | |
| buffer_manager()->ValidateAndDoGetBufferParameteriv( | |
| &state_, error_state_.get(), target, pname, params); | |
| } | |
| void GLES2DecoderImpl::DoBindAttribLocation(GLuint program_id, | |
| GLuint index, | |
| const std::string& name) { | |
| if (!StringIsValidForGLES(name)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glBindAttribLocation", "Invalid character"); | |
| return; | |
| } | |
| if (ProgramManager::HasBuiltInPrefix(name)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, "glBindAttribLocation", "reserved prefix"); | |
| return; | |
| } | |
| if (index >= group_->max_vertex_attribs()) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glBindAttribLocation", "index out of range"); | |
| return; | |
| } | |
| Program* program = GetProgramInfoNotShader( | |
| program_id, "glBindAttribLocation"); | |
| if (!program) { | |
| return; | |
| } | |
| // At this point, the program's shaders may not be translated yet, | |
| // therefore, we may not find the hashed attribute name. | |
| // glBindAttribLocation call with original name is useless. | |
| // So instead, we simply cache the binding, and then call | |
| // Program::ExecuteBindAttribLocationCalls() right before link. | |
| program->SetAttribLocationBinding(name, static_cast<GLint>(index)); | |
| } | |
| error::Error GLES2DecoderImpl::HandleBindAttribLocationBucket( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::BindAttribLocationBucket& c = | |
| *static_cast<const volatile gles2::cmds::BindAttribLocationBucket*>( | |
| cmd_data); | |
| GLuint program = static_cast<GLuint>(c.program); | |
| GLuint index = static_cast<GLuint>(c.index); | |
| Bucket* bucket = GetBucket(c.name_bucket_id); | |
| if (!bucket || bucket->size() == 0) { | |
| return error::kInvalidArguments; | |
| } | |
| std::string name_str; | |
| if (!bucket->GetAsString(&name_str)) { | |
| return error::kInvalidArguments; | |
| } | |
| DoBindAttribLocation(program, index, name_str); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::DoBindFragDataLocation(GLuint program_id, | |
| GLuint colorName, | |
| const std::string& name) { | |
| const char kFunctionName[] = "glBindFragDataLocationEXT"; | |
| if (!StringIsValidForGLES(name)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, kFunctionName, "invalid character"); | |
| return error::kNoError; | |
| } | |
| if (ProgramManager::HasBuiltInPrefix(name)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, kFunctionName, "reserved prefix"); | |
| return error::kNoError; | |
| } | |
| if (colorName >= group_->max_draw_buffers()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, kFunctionName, | |
| "colorName out of range"); | |
| return error::kNoError; | |
| } | |
| Program* program = GetProgramInfoNotShader(program_id, kFunctionName); | |
| if (!program) { | |
| return error::kNoError; | |
| } | |
| program->SetProgramOutputLocationBinding(name, colorName); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleBindFragDataLocationEXTBucket( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| if (!features().ext_blend_func_extended) { | |
| return error::kUnknownCommand; | |
| } | |
| const volatile gles2::cmds::BindFragDataLocationEXTBucket& c = | |
| *static_cast<const volatile gles2::cmds::BindFragDataLocationEXTBucket*>( | |
| cmd_data); | |
| GLuint program = static_cast<GLuint>(c.program); | |
| GLuint colorNumber = static_cast<GLuint>(c.colorNumber); | |
| Bucket* bucket = GetBucket(c.name_bucket_id); | |
| if (!bucket || bucket->size() == 0) { | |
| return error::kInvalidArguments; | |
| } | |
| std::string name_str; | |
| if (!bucket->GetAsString(&name_str)) { | |
| return error::kInvalidArguments; | |
| } | |
| return DoBindFragDataLocation(program, colorNumber, name_str); | |
| } | |
| error::Error GLES2DecoderImpl::DoBindFragDataLocationIndexed( | |
| GLuint program_id, | |
| GLuint colorName, | |
| GLuint index, | |
| const std::string& name) { | |
| const char kFunctionName[] = "glBindFragDataLocationIndexEXT"; | |
| if (!StringIsValidForGLES(name)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, kFunctionName, "invalid character"); | |
| return error::kNoError; | |
| } | |
| if (ProgramManager::HasBuiltInPrefix(name)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, kFunctionName, "reserved prefix"); | |
| return error::kNoError; | |
| } | |
| if (index != 0 && index != 1) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, kFunctionName, "index out of range"); | |
| return error::kNoError; | |
| } | |
| if ((index == 0 && colorName >= group_->max_draw_buffers()) || | |
| (index == 1 && colorName >= group_->max_dual_source_draw_buffers())) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, kFunctionName, | |
| "colorName out of range for the color index"); | |
| return error::kNoError; | |
| } | |
| Program* program = GetProgramInfoNotShader(program_id, kFunctionName); | |
| if (!program) { | |
| return error::kNoError; | |
| } | |
| program->SetProgramOutputLocationIndexedBinding(name, colorName, index); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleBindFragDataLocationIndexedEXTBucket( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| if (!features().ext_blend_func_extended) { | |
| return error::kUnknownCommand; | |
| } | |
| const volatile gles2::cmds::BindFragDataLocationIndexedEXTBucket& c = | |
| *static_cast< | |
| const volatile gles2::cmds::BindFragDataLocationIndexedEXTBucket*>( | |
| cmd_data); | |
| GLuint program = static_cast<GLuint>(c.program); | |
| GLuint colorNumber = static_cast<GLuint>(c.colorNumber); | |
| GLuint index = static_cast<GLuint>(c.index); | |
| Bucket* bucket = GetBucket(c.name_bucket_id); | |
| if (!bucket || bucket->size() == 0) { | |
| return error::kInvalidArguments; | |
| } | |
| std::string name_str; | |
| if (!bucket->GetAsString(&name_str)) { | |
| return error::kInvalidArguments; | |
| } | |
| return DoBindFragDataLocationIndexed(program, colorNumber, index, name_str); | |
| } | |
| void GLES2DecoderImpl::DoBindUniformLocationCHROMIUM(GLuint program_id, | |
| GLint location, | |
| const std::string& name) { | |
| if (!StringIsValidForGLES(name)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, | |
| "glBindUniformLocationCHROMIUM", "Invalid character"); | |
| return; | |
| } | |
| if (ProgramManager::HasBuiltInPrefix(name)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, | |
| "glBindUniformLocationCHROMIUM", "reserved prefix"); | |
| return; | |
| } | |
| if (location < 0 || | |
| static_cast<uint32_t>(location) >= | |
| (group_->max_fragment_uniform_vectors() + | |
| group_->max_vertex_uniform_vectors()) * | |
| 4) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, | |
| "glBindUniformLocationCHROMIUM", "location out of range"); | |
| return; | |
| } | |
| Program* program = GetProgramInfoNotShader( | |
| program_id, "glBindUniformLocationCHROMIUM"); | |
| if (!program) { | |
| return; | |
| } | |
| if (!program->SetUniformLocationBinding(name, location)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, | |
| "glBindUniformLocationCHROMIUM", "location out of range"); | |
| } | |
| } | |
| error::Error GLES2DecoderImpl::HandleBindUniformLocationCHROMIUMBucket( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::BindUniformLocationCHROMIUMBucket& c = | |
| *static_cast< | |
| const volatile gles2::cmds::BindUniformLocationCHROMIUMBucket*>( | |
| cmd_data); | |
| GLuint program = static_cast<GLuint>(c.program); | |
| GLint location = static_cast<GLint>(c.location); | |
| Bucket* bucket = GetBucket(c.name_bucket_id); | |
| if (!bucket || bucket->size() == 0) { | |
| return error::kInvalidArguments; | |
| } | |
| std::string name_str; | |
| if (!bucket->GetAsString(&name_str)) { | |
| return error::kInvalidArguments; | |
| } | |
| DoBindUniformLocationCHROMIUM(program, location, name_str); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleDeleteShader( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::DeleteShader& c = | |
| *static_cast<const volatile gles2::cmds::DeleteShader*>(cmd_data); | |
| GLuint client_id = c.shader; | |
| if (client_id) { | |
| Shader* shader = GetShader(client_id); | |
| if (shader) { | |
| if (!shader->IsDeleted()) { | |
| shader_manager()->Delete(shader); | |
| } | |
| } else { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glDeleteShader", "unknown shader"); | |
| } | |
| } | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleDeleteProgram( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::DeleteProgram& c = | |
| *static_cast<const volatile gles2::cmds::DeleteProgram*>(cmd_data); | |
| GLuint client_id = c.program; | |
| if (client_id) { | |
| Program* program = GetProgram(client_id); | |
| if (program) { | |
| if (!program->IsDeleted()) { | |
| program_manager()->MarkAsDeleted(shader_manager(), program); | |
| } | |
| } else { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glDeleteProgram", "unknown program"); | |
| } | |
| } | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::DoClear(GLbitfield mask) { | |
| const char* func_name = "glClear"; | |
| if (mask & | |
| ~(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name, "invalid mask"); | |
| return error::kNoError; | |
| } | |
| if (CheckBoundDrawFramebufferValid(func_name)) { | |
| ApplyDirtyState(); | |
| if (workarounds().gl_clear_broken) { | |
| if (!BoundFramebufferHasDepthAttachment()) | |
| mask &= ~GL_DEPTH_BUFFER_BIT; | |
| if (!BoundFramebufferHasStencilAttachment()) | |
| mask &= ~GL_STENCIL_BUFFER_BIT; | |
| ClearFramebufferForWorkaround(mask); | |
| return error::kNoError; | |
| } | |
| if (mask & GL_COLOR_BUFFER_BIT) { | |
| Framebuffer* framebuffer = | |
| framebuffer_state_.bound_draw_framebuffer.get(); | |
| if (framebuffer && framebuffer->ContainsActiveIntegerAttachments()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "can't be called on integer buffers"); | |
| return error::kNoError; | |
| } | |
| } | |
| AdjustDrawBuffers(); | |
| api()->glClearFn(mask); | |
| } | |
| return error::kNoError; | |
| } | |
| void GLES2DecoderImpl::DoClearBufferiv(GLenum buffer, | |
| GLint drawbuffer, | |
| const volatile GLint* value) { | |
| const char* func_name = "glClearBufferiv"; | |
| if (!CheckBoundDrawFramebufferValid(func_name)) | |
| return; | |
| ApplyDirtyState(); | |
| if (buffer == GL_COLOR) { | |
| if (drawbuffer < 0 || | |
| drawbuffer >= static_cast<GLint>(group_->max_draw_buffers())) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name, "invalid drawBuffer"); | |
| return; | |
| } | |
| GLenum internal_format = | |
| GetBoundColorDrawBufferInternalFormat(drawbuffer); | |
| if (!GLES2Util::IsSignedIntegerFormat(internal_format)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "can only be called on signed integer buffers"); | |
| return; | |
| } | |
| } else { | |
| DCHECK(buffer == GL_STENCIL); | |
| if (drawbuffer != 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name, "invalid drawBuffer"); | |
| return; | |
| } | |
| if (!BoundFramebufferHasStencilAttachment()) { | |
| return; | |
| } | |
| } | |
| MarkDrawBufferAsCleared(buffer, drawbuffer); | |
| api()->glClearBufferivFn(buffer, drawbuffer, const_cast<const GLint*>(value)); | |
| } | |
| void GLES2DecoderImpl::DoClearBufferuiv(GLenum buffer, | |
| GLint drawbuffer, | |
| const volatile GLuint* value) { | |
| const char* func_name = "glClearBufferuiv"; | |
| if (!CheckBoundDrawFramebufferValid(func_name)) | |
| return; | |
| ApplyDirtyState(); | |
| if (drawbuffer < 0 || | |
| drawbuffer >= static_cast<GLint>(group_->max_draw_buffers())) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name, "invalid drawBuffer"); | |
| return; | |
| } | |
| GLenum internal_format = | |
| GetBoundColorDrawBufferInternalFormat(drawbuffer); | |
| if (!GLES2Util::IsUnsignedIntegerFormat(internal_format)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "can only be called on unsigned integer buffers"); | |
| return; | |
| } | |
| MarkDrawBufferAsCleared(buffer, drawbuffer); | |
| api()->glClearBufferuivFn(buffer, drawbuffer, | |
| const_cast<const GLuint*>(value)); | |
| } | |
| void GLES2DecoderImpl::DoClearBufferfv(GLenum buffer, | |
| GLint drawbuffer, | |
| const volatile GLfloat* value) { | |
| const char* func_name = "glClearBufferfv"; | |
| if (!CheckBoundDrawFramebufferValid(func_name)) | |
| return; | |
| ApplyDirtyState(); | |
| if (buffer == GL_COLOR) { | |
| if (drawbuffer < 0 || | |
| drawbuffer >= static_cast<GLint>(group_->max_draw_buffers())) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name, "invalid drawBuffer"); | |
| return; | |
| } | |
| GLenum internal_format = | |
| GetBoundColorDrawBufferInternalFormat(drawbuffer); | |
| if (GLES2Util::IsIntegerFormat(internal_format)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "can only be called on float buffers"); | |
| return; | |
| } | |
| } else { | |
| DCHECK(buffer == GL_DEPTH); | |
| if (drawbuffer != 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name, "invalid drawBuffer"); | |
| return; | |
| } | |
| if (!BoundFramebufferHasDepthAttachment()) { | |
| return; | |
| } | |
| } | |
| MarkDrawBufferAsCleared(buffer, drawbuffer); | |
| api()->glClearBufferfvFn(buffer, drawbuffer, | |
| const_cast<const GLfloat*>(value)); | |
| } | |
| void GLES2DecoderImpl::DoClearBufferfi( | |
| GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) { | |
| const char* func_name = "glClearBufferfi"; | |
| if (!CheckBoundDrawFramebufferValid(func_name)) | |
| return; | |
| ApplyDirtyState(); | |
| if (drawbuffer != 0) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, func_name, "invalid drawBuffer"); | |
| return; | |
| } | |
| if (!BoundFramebufferHasDepthAttachment() && | |
| !BoundFramebufferHasStencilAttachment()) { | |
| return; | |
| } | |
| MarkDrawBufferAsCleared(GL_DEPTH, drawbuffer); | |
| MarkDrawBufferAsCleared(GL_STENCIL, drawbuffer); | |
| api()->glClearBufferfiFn(buffer, drawbuffer, depth, stencil); | |
| } | |
| void GLES2DecoderImpl::DoFramebufferParameteri(GLenum target, | |
| GLenum pname, | |
| GLint param) { | |
| const char* func_name = "glFramebufferParameteri"; | |
| Framebuffer* framebuffer = GetFramebufferInfoForTarget(target); | |
| if (!framebuffer) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, "no framebuffer bound"); | |
| return; | |
| } | |
| api()->glFramebufferParameteriMESAFn(target, pname, param); | |
| } | |
| void GLES2DecoderImpl::DoFramebufferRenderbuffer( | |
| GLenum target, GLenum attachment, GLenum renderbuffertarget, | |
| GLuint client_renderbuffer_id) { | |
| const char* func_name = "glFramebufferRenderbuffer"; | |
| Framebuffer* framebuffer = GetFramebufferInfoForTarget(target); | |
| if (!framebuffer) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, "no framebuffer bound"); | |
| return; | |
| } | |
| GLuint service_id = 0; | |
| Renderbuffer* renderbuffer = nullptr; | |
| if (client_renderbuffer_id) { | |
| renderbuffer = GetRenderbuffer(client_renderbuffer_id); | |
| if (!renderbuffer) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "unknown renderbuffer"); | |
| return; | |
| } | |
| if (!renderbuffer->IsValid()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "renderbuffer never bound or deleted"); | |
| return; | |
| } | |
| service_id = renderbuffer->service_id(); | |
| } | |
| std::vector<GLenum> attachments; | |
| if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) { | |
| attachments.push_back(GL_DEPTH_ATTACHMENT); | |
| attachments.push_back(GL_STENCIL_ATTACHMENT); | |
| } else { | |
| attachments.push_back(attachment); | |
| } | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER(func_name); | |
| for (GLenum attachment_point : attachments) { | |
| api()->glFramebufferRenderbufferEXTFn(target, attachment_point, | |
| renderbuffertarget, service_id); | |
| GLenum error = LOCAL_PEEK_GL_ERROR(func_name); | |
| if (error == GL_NO_ERROR) { | |
| framebuffer->AttachRenderbuffer(attachment_point, renderbuffer); | |
| } | |
| } | |
| if (framebuffer == framebuffer_state_.bound_draw_framebuffer.get()) { | |
| framebuffer_state_.clear_state_dirty = true; | |
| } | |
| OnFboChanged(); | |
| } | |
| void GLES2DecoderImpl::DoDisable(GLenum cap) { | |
| if (SetCapabilityState(cap, false)) { | |
| if (cap == GL_PRIMITIVE_RESTART_FIXED_INDEX && | |
| feature_info_->feature_flags().emulate_primitive_restart_fixed_index) { | |
| // Enable and Disable PRIMITIVE_RESTART only before and after | |
| // DrawElements* for old desktop GL. | |
| return; | |
| } | |
| if (cap == GL_FRAMEBUFFER_SRGB_EXT) { | |
| // Enable and Disable GL_FRAMEBUFFER_SRGB_EXT is done manually in | |
| // CheckBoundDrawFramebufferValid. | |
| return; | |
| } | |
| api()->glDisableFn(cap); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoDisableiOES(GLenum target, GLuint index) { | |
| api()->glDisableiOESFn(target, index); | |
| } | |
| void GLES2DecoderImpl::DoEnable(GLenum cap) { | |
| if (SetCapabilityState(cap, true)) { | |
| if (cap == GL_PRIMITIVE_RESTART_FIXED_INDEX && | |
| feature_info_->feature_flags().emulate_primitive_restart_fixed_index) { | |
| // Enable and Disable PRIMITIVE_RESTART only before and after | |
| // DrawElements* for old desktop GL. | |
| return; | |
| } | |
| if (cap == GL_FRAMEBUFFER_SRGB_EXT) { | |
| // Enable and Disable GL_FRAMEBUFFER_SRGB_EXT is done manually in | |
| // CheckBoundDrawFramebufferValid. | |
| return; | |
| } | |
| api()->glEnableFn(cap); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoEnableiOES(GLenum target, GLuint index) { | |
| api()->glEnableiOESFn(target, index); | |
| } | |
| void GLES2DecoderImpl::DoDepthRangef(GLclampf znear, GLclampf zfar) { | |
| state_.z_near = std::clamp(znear, 0.0f, 1.0f); | |
| state_.z_far = std::clamp(zfar, 0.0f, 1.0f); | |
| api()->glDepthRangeFn(znear, zfar); | |
| } | |
| void GLES2DecoderImpl::DoSampleCoverage(GLclampf value, GLboolean invert) { | |
| state_.sample_coverage_value = std::clamp(value, 0.0f, 1.0f); | |
| state_.sample_coverage_invert = (invert != 0); | |
| api()->glSampleCoverageFn(state_.sample_coverage_value, invert); | |
| } | |
| // Assumes framebuffer is complete. | |
| bool GLES2DecoderImpl::ClearUnclearedAttachments(GLenum target, | |
| Framebuffer* framebuffer) { | |
| bool rasterizer_discard_enabled = state_.enable_flags.rasterizer_discard; | |
| if (rasterizer_discard_enabled) { | |
| state_.SetDeviceCapabilityState(GL_RASTERIZER_DISCARD, false); | |
| } | |
| // Clear textures that we can't use glClear first. These textures will be | |
| // marked as cleared after the call and no longer be part of the following | |
| // code. | |
| if (!framebuffer->ClearUnclearedIntOr3DTexturesOrPartiallyClearedTextures( | |
| this, texture_manager())) { | |
| MarkContextLost(error::kUnknown); | |
| group_->LoseContexts(error::kUnknown); | |
| return false; | |
| } | |
| // Set when a clear issued below failed. A failed clear leaves the attachment | |
| // holding whatever was previously in that GPU memory, so the "cleared" | |
| // bookkeeping must not be committed for it. | |
| bool clear_failed = false; | |
| bool cleared_int_renderbuffers = false; | |
| Framebuffer* draw_framebuffer = GetBoundDrawFramebuffer(); | |
| if (framebuffer->HasUnclearedIntRenderbufferAttachments()) { | |
| if (target == GL_READ_FRAMEBUFFER && draw_framebuffer != framebuffer) { | |
| // TODO(zmo): There is no guarantee that an FBO that is complete on the | |
| // READ attachment will be complete as a DRAW attachment. | |
| BindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer->service_id()); | |
| } | |
| state_.SetDeviceColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); | |
| state_.SetDeviceCapabilityState(GL_SCISSOR_TEST, false); | |
| ClearDeviceWindowRectangles(); | |
| // Drain pre-existing driver errors into the wrapper so they are still | |
| // reported to the client, and so that any error observed by the clears | |
| // below is attributable to those clears. | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER("ClearUnclearedAttachments"); | |
| // TODO(zmo): Assume DrawBuffers() does not affect ClearBuffer(). | |
| GLenum int_clear_error = | |
| framebuffer->ClearUnclearedIntRenderbufferAttachments( | |
| renderbuffer_manager()); | |
| if (int_clear_error != GL_NO_ERROR) { | |
| // Report the failure through the wrapper so that GL_OUT_OF_MEMORY runs | |
| // OnOutOfMemoryError() and lose_context_when_out_of_memory_ applies. | |
| LOCAL_SET_GL_ERROR(int_clear_error, "ClearUnclearedAttachments", | |
| "failed to clear integer renderbuffer attachment"); | |
| clear_failed = true; | |
| } | |
| // Set even when the clear failed: this tracks that the block above | |
| // dirtied GL state (draw framebuffer binding, color mask, scissor test, | |
| // window rectangles), not that the attachments ended up cleared. It is | |
| // what drives the state restoration below, which must run on both the | |
| // success and the failure path. | |
| cleared_int_renderbuffers = true; | |
| } | |
| GLbitfield clear_bits = 0; | |
| bool reset_draw_buffers = false; | |
| bool rebound_draw_for_clear = cleared_int_renderbuffers; | |
| if (!clear_failed) { | |
| if (framebuffer->HasUnclearedColorAttachments()) { | |
| // We should always use alpha == 0 here, because 1) some draw buffers may | |
| // have alpha and some may not; 2) we won't have the same situation as the | |
| // back buffer where alpha channel exists but is not requested. | |
| api()->glClearColorFn(0.0f, 0.0f, 0.0f, 0.0f); | |
| state_.SetDeviceColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); | |
| clear_bits |= GL_COLOR_BUFFER_BIT; | |
| if (SupportsDrawBuffers()) { | |
| // Ensure |framebuffer| is bound as DRAW before preparing draw buffers. | |
| // Otherwise glDrawBuffersARB mutates the wrong FBO's state, causing | |
| // the glClear to skip clearing uncleared attachments. | |
| if (!rebound_draw_for_clear && target == GL_READ_FRAMEBUFFER && | |
| draw_framebuffer != framebuffer) { | |
| BindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer->service_id()); | |
| rebound_draw_for_clear = true; | |
| } | |
| reset_draw_buffers = | |
| framebuffer | |
| ->PrepareDrawBuffersForClearingUninitializedAttachments(); | |
| } | |
| } | |
| const Framebuffer::Attachment* depth_attachment = | |
| framebuffer->GetAttachment(GL_DEPTH_ATTACHMENT); | |
| const Framebuffer::Attachment* stencil_attachment = | |
| framebuffer->GetAttachment(GL_STENCIL_ATTACHMENT); | |
| bool clear_depth = depth_attachment && !depth_attachment->cleared(); | |
| bool clear_stencil = stencil_attachment && !stencil_attachment->cleared(); | |
| // A packed depth-stencil image attached at only one of the depth/stencil | |
| // points must be bound and cleared at both points so that both components | |
| // are initialized before the image is marked as cleared. | |
| GLenum filled_depth_stencil_point = 0; | |
| if (clear_depth && !stencil_attachment && | |
| (GLES2Util::GetChannelsForFormat(depth_attachment->internal_format()) & | |
| GLES2Util::kStencil) != 0) { | |
| filled_depth_stencil_point = GL_STENCIL_ATTACHMENT; | |
| Framebuffer::BindAttachmentToPoint(target, GL_STENCIL_ATTACHMENT, | |
| depth_attachment); | |
| clear_stencil = true; | |
| } else if (clear_stencil && !depth_attachment && | |
| (GLES2Util::GetChannelsForFormat( | |
| stencil_attachment->internal_format()) & | |
| GLES2Util::kDepth) != 0) { | |
| filled_depth_stencil_point = GL_DEPTH_ATTACHMENT; | |
| Framebuffer::BindAttachmentToPoint(target, GL_DEPTH_ATTACHMENT, | |
| stencil_attachment); | |
| clear_depth = true; | |
| } | |
| if (clear_stencil) { | |
| api()->glClearStencilFn(0); | |
| state_.SetDeviceStencilMaskSeparate(GL_FRONT, kDefaultStencilMask); | |
| state_.SetDeviceStencilMaskSeparate(GL_BACK, kDefaultStencilMask); | |
| clear_bits |= GL_STENCIL_BUFFER_BIT; | |
| } | |
| if (clear_depth) { | |
| api()->glClearDepthFn(1.0f); | |
| state_.SetDeviceDepthMask(GL_TRUE); | |
| clear_bits |= GL_DEPTH_BUFFER_BIT; | |
| } | |
| if (clear_bits) { | |
| if (!rebound_draw_for_clear && target == GL_READ_FRAMEBUFFER && | |
| draw_framebuffer != framebuffer) { | |
| // TODO(zmo): There is no guarantee that an FBO that is complete on the | |
| // READ attachment will be complete as a DRAW attachment. | |
| BindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer->service_id()); | |
| } | |
| state_.SetDeviceCapabilityState(GL_SCISSOR_TEST, false); | |
| ClearDeviceWindowRectangles(); | |
| if (workarounds().gl_clear_broken) { | |
| clear_failed = !ClearFramebufferForWorkaround(clear_bits); | |
| } else { | |
| // Drain any pre-existing driver errors so the check below only reflects | |
| // errors generated by this clear. | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER("ClearUnclearedAttachments"); | |
| api()->glClearFn(clear_bits); | |
| // GL_OUT_OF_MEMORY can be raised by any command (ES 3.2 section 2.3.1), | |
| // and leaves the results of that command undefined. A clear that failed | |
| // this way leaves the attachment holding whatever was previously in | |
| // that GPU memory - drivers that defer physical allocation until first | |
| // use can fail here under VRAM pressure. Routing the error through | |
| // PeekGLError also makes OnOutOfMemoryError() fire so | |
| // lose_context_when_out_of_memory_ applies. | |
| clear_failed = | |
| LOCAL_PEEK_GL_ERROR("ClearUnclearedAttachments") != GL_NO_ERROR; | |
| } | |
| } | |
| if (filled_depth_stencil_point) { | |
| Framebuffer::BindAttachmentToPoint(target, filled_depth_stencil_point, | |
| nullptr); | |
| } | |
| } | |
| if (cleared_int_renderbuffers || clear_bits) { | |
| if (reset_draw_buffers) | |
| framebuffer->RestoreDrawBuffers(); | |
| RestoreClearState(); | |
| if (target == GL_READ_FRAMEBUFFER && draw_framebuffer != framebuffer) { | |
| GLuint service_id = draw_framebuffer ? draw_framebuffer->service_id() : | |
| GetBackbufferServiceId(); | |
| BindFramebuffer(GL_DRAW_FRAMEBUFFER, service_id); | |
| } | |
| } | |
| if (rasterizer_discard_enabled) { | |
| state_.SetDeviceCapabilityState(GL_RASTERIZER_DISCARD, true); | |
| } | |
| // Only commit the cleared state once the clear is known to have landed. | |
| // Marking attachments cleared after a failed clear would let a subsequent | |
| // ReadPixels()/draw read uninitialized GPU memory and hand it to the client. | |
| if (clear_failed) { | |
| return false; | |
| } | |
| framebuffer_manager()->MarkAttachmentsAsCleared( | |
| framebuffer, renderbuffer_manager(), texture_manager()); | |
| return true; | |
| } | |
| void GLES2DecoderImpl::RestoreClearState() { | |
| framebuffer_state_.clear_state_dirty = true; | |
| api()->glClearColorFn(state_.color_clear_red, state_.color_clear_green, | |
| state_.color_clear_blue, state_.color_clear_alpha); | |
| api()->glClearStencilFn(state_.stencil_clear); | |
| api()->glClearDepthFn(state_.depth_clear); | |
| state_.SetDeviceCapabilityState(GL_SCISSOR_TEST, | |
| state_.enable_flags.scissor_test); | |
| RestoreDeviceWindowRectangles(); | |
| api()->glScissorFn(state_.scissor_x, state_.scissor_y, state_.scissor_width, | |
| state_.scissor_height); | |
| } | |
| GLenum GLES2DecoderImpl::DoCheckFramebufferStatus(GLenum target) { | |
| Framebuffer* framebuffer = | |
| GetFramebufferInfoForTarget(target); | |
| if (!framebuffer) { | |
| return GL_FRAMEBUFFER_COMPLETE; | |
| } | |
| GLenum completeness = framebuffer->IsPossiblyComplete(feature_info_.get()); | |
| if (completeness != GL_FRAMEBUFFER_COMPLETE) { | |
| return completeness; | |
| } | |
| return framebuffer->GetStatus(texture_manager(), target); | |
| } | |
| void GLES2DecoderImpl::DoFramebufferTexture2D( | |
| GLenum target, GLenum attachment, GLenum textarget, | |
| GLuint client_texture_id, GLint level) { | |
| DoFramebufferTexture2DCommon( | |
| "glFramebufferTexture2D", target, attachment, | |
| textarget, client_texture_id, level, 0); | |
| } | |
| void GLES2DecoderImpl::DoFramebufferTexture2DMultisample( | |
| GLenum target, GLenum attachment, GLenum textarget, | |
| GLuint client_texture_id, GLint level, GLsizei samples) { | |
| DoFramebufferTexture2DCommon( | |
| "glFramebufferTexture2DMultisample", target, attachment, | |
| textarget, client_texture_id, level, samples); | |
| } | |
| void GLES2DecoderImpl::DoFramebufferTexture2DCommon( | |
| const char* name, GLenum target, GLenum attachment, GLenum textarget, | |
| GLuint client_texture_id, GLint level, GLsizei samples) { | |
| if (samples > renderbuffer_manager()->max_samples()) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, | |
| "glFramebufferTexture2DMultisample", "samples too large"); | |
| return; | |
| } | |
| Framebuffer* framebuffer = GetFramebufferInfoForTarget(target); | |
| if (!framebuffer) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, | |
| name, "no framebuffer bound."); | |
| return; | |
| } | |
| GLuint service_id = 0; | |
| TextureRef* texture_ref = nullptr; | |
| if (client_texture_id) { | |
| texture_ref = GetTexture(client_texture_id); | |
| if (!texture_ref) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, | |
| name, "unknown texture_ref"); | |
| return; | |
| } | |
| GLenum texture_target = texture_ref->texture()->target(); | |
| if (texture_target != GLES2Util::GLFaceTargetToTextureTarget(textarget)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, | |
| name, "Attachment textarget doesn't match texture target"); | |
| return; | |
| } | |
| service_id = texture_ref->service_id(); | |
| } | |
| bool valid_target = false; | |
| if (texture_ref) { | |
| valid_target = texture_manager()->ValidForTextureTarget( | |
| texture_ref->texture(), level, 0, 0, 1); | |
| } else { | |
| valid_target = texture_manager()->ValidForTarget(textarget, level, 0, 0, 1); | |
| } | |
| if ((level > 0 && !feature_info_->IsWebGL2OrES3Context() && | |
| !(fbo_render_mipmap_explicitly_enabled_ && | |
| feature_info_->feature_flags().oes_fbo_render_mipmap)) || | |
| !valid_target) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, | |
| name, "level out of range"); | |
| return; | |
| } | |
| std::vector<GLenum> attachments; | |
| if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) { | |
| attachments.push_back(GL_DEPTH_ATTACHMENT); | |
| attachments.push_back(GL_STENCIL_ATTACHMENT); | |
| } else { | |
| attachments.push_back(attachment); | |
| } | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER(name); | |
| for (size_t ii = 0; ii < attachments.size(); ++ii) { | |
| if (0 == samples) { | |
| api()->glFramebufferTexture2DEXTFn(target, attachments[ii], textarget, | |
| service_id, level); | |
| } else { | |
| api()->glFramebufferTexture2DMultisampleEXTFn( | |
| target, attachments[ii], textarget, service_id, level, samples); | |
| } | |
| GLenum error = LOCAL_PEEK_GL_ERROR(name); | |
| if (error == GL_NO_ERROR) { | |
| framebuffer->AttachTexture(attachments[ii], texture_ref, textarget, level, | |
| samples); | |
| } | |
| } | |
| if (framebuffer == framebuffer_state_.bound_draw_framebuffer.get()) { | |
| framebuffer_state_.clear_state_dirty = true; | |
| } | |
| OnFboChanged(); | |
| } | |
| void GLES2DecoderImpl::DoFramebufferTextureLayer( | |
| GLenum target, GLenum attachment, GLuint client_texture_id, | |
| GLint level, GLint layer) { | |
| const char* function_name = "glFramebufferTextureLayer"; | |
| TextureRef* texture_ref = nullptr; | |
| Framebuffer* framebuffer = GetFramebufferInfoForTarget(target); | |
| if (!framebuffer) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, function_name, "no framebuffer bound."); | |
| return; | |
| } | |
| GLuint service_id = 0; | |
| GLenum texture_target = 0; | |
| if (client_texture_id) { | |
| texture_ref = GetTexture(client_texture_id); | |
| if (!texture_ref) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, function_name, "unknown texture"); | |
| return; | |
| } | |
| service_id = texture_ref->service_id(); | |
| texture_target = texture_ref->texture()->target(); | |
| switch (texture_target) { | |
| case GL_TEXTURE_3D: | |
| case GL_TEXTURE_2D_ARRAY: | |
| break; | |
| default: | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "texture is neither TEXTURE_3D nor TEXTURE_2D_ARRAY"); | |
| return; | |
| } | |
| if (!texture_manager()->ValidForTextureTarget(texture_ref->texture(), level, | |
| 0, 0, layer)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, function_name, "invalid level or layer"); | |
| return; | |
| } | |
| } | |
| api()->glFramebufferTextureLayerFn(target, attachment, service_id, level, | |
| layer); | |
| if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) { | |
| framebuffer->AttachTextureLayer( | |
| GL_DEPTH_ATTACHMENT, texture_ref, texture_target, level, layer); | |
| framebuffer->AttachTextureLayer( | |
| GL_STENCIL_ATTACHMENT, texture_ref, texture_target, level, layer); | |
| } else { | |
| framebuffer->AttachTextureLayer( | |
| attachment, texture_ref, texture_target, level, layer); | |
| } | |
| if (framebuffer == framebuffer_state_.bound_draw_framebuffer.get()) { | |
| framebuffer_state_.clear_state_dirty = true; | |
| } | |
| } | |
| void GLES2DecoderImpl::DoFramebufferTextureMultiviewOVR( | |
| GLenum target, | |
| GLenum attachment, | |
| GLuint client_texture_id, | |
| GLint level, | |
| GLint base_view_index, | |
| GLsizei num_views) { | |
| // This is only supported in passthrough command buffer. | |
| NOTREACHED(); | |
| } | |
| void GLES2DecoderImpl::DoGetFramebufferAttachmentParameteriv( | |
| GLenum target, | |
| GLenum attachment, | |
| GLenum pname, | |
| GLint* params, | |
| GLsizei params_size) { | |
| const char kFunctionName[] = "glGetFramebufferAttachmentParameteriv"; | |
| Framebuffer* framebuffer = GetFramebufferInfoForTarget(target); | |
| if (!framebuffer) { | |
| if (!feature_info_->IsWebGL2OrES3Context()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, kFunctionName, | |
| "no framebuffer bound"); | |
| return; | |
| } | |
| if (!validators_->backbuffer_attachment.IsValid(attachment)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, kFunctionName, | |
| "invalid attachment for backbuffer"); | |
| return; | |
| } | |
| switch (pname) { | |
| case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE: | |
| *params = static_cast<GLint>(GL_FRAMEBUFFER_DEFAULT); | |
| return; | |
| case GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE: | |
| case GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE: | |
| case GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE: | |
| case GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE: | |
| case GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE: | |
| case GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE: | |
| case GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE: | |
| case GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING: | |
| // Delegate to underlying driver. | |
| break; | |
| default: | |
| LOCAL_SET_GL_ERROR(GL_INVALID_ENUM, kFunctionName, | |
| "invalid pname for backbuffer"); | |
| return; | |
| } | |
| if (GetBackbufferServiceId() != 0) { // Emulated backbuffer. | |
| switch (attachment) { | |
| case GL_BACK: | |
| attachment = GL_COLOR_ATTACHMENT0; | |
| break; | |
| case GL_DEPTH: | |
| attachment = GL_DEPTH_ATTACHMENT; | |
| break; | |
| case GL_STENCIL: | |
| attachment = GL_STENCIL_ATTACHMENT; | |
| break; | |
| default: | |
| NOTREACHED(); | |
| } | |
| } | |
| } else { | |
| if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) { | |
| const Framebuffer::Attachment* depth = | |
| framebuffer->GetAttachment(GL_DEPTH_ATTACHMENT); | |
| const Framebuffer::Attachment* stencil = | |
| framebuffer->GetAttachment(GL_STENCIL_ATTACHMENT); | |
| if ((!depth && !stencil) || | |
| (depth && stencil && depth->IsSameAttachment(stencil))) { | |
| attachment = GL_DEPTH_ATTACHMENT; | |
| } else { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, kFunctionName, | |
| "depth and stencil attachment mismatch"); | |
| return; | |
| } | |
| } | |
| } | |
| if (pname == GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_SAMPLES_EXT && | |
| features().use_img_for_multisampled_render_to_texture) { | |
| pname = GL_TEXTURE_SAMPLES_IMG; | |
| } | |
| if (pname == GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME) { | |
| DCHECK(framebuffer); | |
| // If we query from the driver, it will be service ID; however, we need to | |
| // return the client ID here. | |
| const Framebuffer::Attachment* attachment_object = | |
| framebuffer->GetAttachment(attachment); | |
| *params = attachment_object ? attachment_object->object_name() : 0; | |
| return; | |
| } | |
| api()->glGetFramebufferAttachmentParameterivEXTFn(target, attachment, pname, | |
| params); | |
| // We didn't perform a full error check before gl call. | |
| LOCAL_PEEK_GL_ERROR(kFunctionName); | |
| } | |
| void GLES2DecoderImpl::DoGetRenderbufferParameteriv(GLenum target, | |
| GLenum pname, | |
| GLint* params, | |
| GLsizei params_size) { | |
| Renderbuffer* renderbuffer = | |
| GetRenderbufferInfoForTarget(GL_RENDERBUFFER); | |
| if (!renderbuffer) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, | |
| "glGetRenderbufferParameteriv", "no renderbuffer bound"); | |
| return; | |
| } | |
| EnsureRenderbufferBound(); | |
| switch (pname) { | |
| case GL_RENDERBUFFER_INTERNAL_FORMAT: | |
| *params = renderbuffer->internal_format(); | |
| break; | |
| case GL_RENDERBUFFER_WIDTH: | |
| *params = renderbuffer->width(); | |
| break; | |
| case GL_RENDERBUFFER_HEIGHT: | |
| *params = renderbuffer->height(); | |
| break; | |
| case GL_RENDERBUFFER_SAMPLES_EXT: | |
| if (features().use_img_for_multisampled_render_to_texture) { | |
| api()->glGetRenderbufferParameterivEXTFn( | |
| target, GL_RENDERBUFFER_SAMPLES_IMG, params); | |
| } else { | |
| api()->glGetRenderbufferParameterivEXTFn( | |
| target, GL_RENDERBUFFER_SAMPLES_EXT, params); | |
| } | |
| break; | |
| default: | |
| api()->glGetRenderbufferParameterivEXTFn(target, pname, params); | |
| break; | |
| } | |
| } | |
| void GLES2DecoderImpl::DoBlitFramebufferCHROMIUM( | |
| GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, | |
| GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, | |
| GLbitfield mask, GLenum filter) { | |
| const char* func_name = "glBlitFramebufferCHROMIUM"; | |
| if (!CheckFramebufferValid(GetBoundDrawFramebuffer(), | |
| GetDrawFramebufferTarget(), | |
| GL_INVALID_FRAMEBUFFER_OPERATION, func_name)) { | |
| return; | |
| } | |
| // We need to get this before checking if the read framebuffer is valid. | |
| // Checking the read framebuffer may clear attachments which would mark the | |
| // draw framebuffer as incomplete. Framebuffer::GetFramebufferValidSize() | |
| // requires the framebuffer to be complete. | |
| gfx::Size draw_size = GetBoundDrawFramebufferSize(); | |
| if (!CheckFramebufferValid(GetBoundReadFramebuffer(), | |
| GetReadFramebufferTarget(), | |
| GL_INVALID_FRAMEBUFFER_OPERATION, func_name)) { | |
| return; | |
| } | |
| if (GetBoundFramebufferSamples(GL_DRAW_FRAMEBUFFER) > 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "destination framebuffer is multisampled"); | |
| return; | |
| } | |
| GLsizei read_buffer_samples = GetBoundFramebufferSamples(GL_READ_FRAMEBUFFER); | |
| if (read_buffer_samples > 0 && | |
| (srcX0 != dstX0 || srcY0 != dstY0 || srcX1 != dstX1 || srcY1 != dstY1)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "src framebuffer is multisampled, but src/dst regions are different"); | |
| return; | |
| } | |
| // If color/depth/stencil buffer have no image, we can remove corresponding | |
| // bitfield from mask and return early if mask equals to 0. | |
| // But validations should be done against the original mask. | |
| GLbitfield mask_blit = mask; | |
| // Detect that designated read/depth/stencil buffer in read framebuffer miss | |
| // image, and the corresponding buffers in draw framebuffer have image. | |
| bool read_framebuffer_miss_image = false; | |
| // Check whether read framebuffer and draw framebuffer have identical image | |
| // TODO(yunchao): consider doing something like CheckFramebufferStatus(). | |
| // We cache the validation results, and if read_framebuffer doesn't change, | |
| // draw_framebuffer doesn't change, then use the cached status. | |
| enum FeedbackLoopState { | |
| FeedbackLoopTrue, | |
| FeedbackLoopFalse, | |
| FeedbackLoopUnknown | |
| }; | |
| FeedbackLoopState is_feedback_loop = FeedbackLoopUnknown; | |
| Framebuffer* read_framebuffer = | |
| framebuffer_state_.bound_read_framebuffer.get(); | |
| Framebuffer* draw_framebuffer = | |
| framebuffer_state_.bound_draw_framebuffer.get(); | |
| // If both read framebuffer and draw framebuffer are default framebuffer, | |
| // They always have identical image. Otherwise, if one of read framebuffer | |
| // and draw framebuffe is default framebuffer, but the other is fbo, they | |
| // always have no identical image. | |
| if (!read_framebuffer && !draw_framebuffer) { | |
| is_feedback_loop = FeedbackLoopTrue; | |
| } else if (!read_framebuffer || !draw_framebuffer) { | |
| is_feedback_loop = FeedbackLoopFalse; | |
| if (read_framebuffer) { | |
| if (((mask & GL_COLOR_BUFFER_BIT) != 0 && | |
| !GetBoundReadFramebufferInternalFormat()) || | |
| ((mask & GL_DEPTH_BUFFER_BIT) != 0 && | |
| !read_framebuffer->GetAttachment(GL_DEPTH_ATTACHMENT) && | |
| BoundFramebufferHasDepthAttachment()) || | |
| ((mask & GL_STENCIL_BUFFER_BIT) != 0 && | |
| !read_framebuffer->GetAttachment(GL_STENCIL_ATTACHMENT) && | |
| BoundFramebufferHasStencilAttachment())) { | |
| read_framebuffer_miss_image = true; | |
| } | |
| } | |
| } else { | |
| DCHECK(read_framebuffer && draw_framebuffer); | |
| if ((mask & GL_DEPTH_BUFFER_BIT) != 0) { | |
| const Framebuffer::Attachment* depth_buffer_read = | |
| read_framebuffer->GetAttachment(GL_DEPTH_ATTACHMENT); | |
| const Framebuffer::Attachment* depth_buffer_draw = | |
| draw_framebuffer->GetAttachment(GL_DEPTH_ATTACHMENT); | |
| if (!depth_buffer_draw || !depth_buffer_read) { | |
| mask_blit &= ~GL_DEPTH_BUFFER_BIT; | |
| if (depth_buffer_draw) { | |
| read_framebuffer_miss_image = true; | |
| } | |
| } else if (depth_buffer_draw->IsSameAttachment(depth_buffer_read)) { | |
| is_feedback_loop = FeedbackLoopTrue; | |
| } | |
| } | |
| if ((mask & GL_STENCIL_BUFFER_BIT) != 0) { | |
| const Framebuffer::Attachment* stencil_buffer_read = | |
| read_framebuffer->GetAttachment(GL_STENCIL_ATTACHMENT); | |
| const Framebuffer::Attachment* stencil_buffer_draw = | |
| draw_framebuffer->GetAttachment(GL_STENCIL_ATTACHMENT); | |
| if (!stencil_buffer_draw || !stencil_buffer_read) { | |
| mask_blit &= ~GL_STENCIL_BUFFER_BIT; | |
| if (stencil_buffer_draw) { | |
| read_framebuffer_miss_image = true; | |
| } | |
| } else if (stencil_buffer_draw->IsSameAttachment(stencil_buffer_read)) { | |
| is_feedback_loop = FeedbackLoopTrue; | |
| } | |
| } | |
| } | |
| GLenum src_internal_format = GetBoundReadFramebufferInternalFormat(); | |
| GLenum src_type = GetBoundReadFramebufferTextureType(); | |
| bool read_buffer_has_srgb = GLES2Util::GetColorEncodingFromInternalFormat( | |
| src_internal_format) == GL_SRGB; | |
| bool draw_buffers_has_srgb = false; | |
| if ((mask & GL_COLOR_BUFFER_BIT) != 0) { | |
| bool is_src_signed_int = | |
| GLES2Util::IsSignedIntegerFormat(src_internal_format); | |
| bool is_src_unsigned_int = | |
| GLES2Util::IsUnsignedIntegerFormat(src_internal_format); | |
| DCHECK(!is_src_signed_int || !is_src_unsigned_int); | |
| if ((is_src_signed_int || is_src_unsigned_int) && filter == GL_LINEAR) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "invalid filter for integer format"); | |
| return; | |
| } | |
| GLenum src_sized_format = | |
| GLES2Util::ConvertToSizedFormat(src_internal_format, src_type); | |
| DCHECK(read_framebuffer || (is_feedback_loop != FeedbackLoopUnknown)); | |
| const Framebuffer::Attachment* read_buffer = | |
| is_feedback_loop == FeedbackLoopUnknown ? | |
| read_framebuffer->GetReadBufferAttachment() : nullptr; | |
| bool draw_buffer_has_image = false; | |
| for (uint32_t ii = 0; ii < group_->max_draw_buffers(); ++ii) { | |
| GLenum dst_format = GetBoundColorDrawBufferInternalFormat( | |
| static_cast<GLint>(ii)); | |
| GLenum dst_type = GetBoundColorDrawBufferType(static_cast<GLint>(ii)); | |
| if (dst_format == 0) | |
| continue; | |
| draw_buffer_has_image = true; | |
| if (!src_internal_format) { | |
| read_framebuffer_miss_image = true; | |
| } | |
| if (GLES2Util::GetColorEncodingFromInternalFormat(dst_format) == GL_SRGB) | |
| draw_buffers_has_srgb = true; | |
| if (read_buffer_samples > 0 && | |
| (src_sized_format != | |
| GLES2Util::ConvertToSizedFormat(dst_format, dst_type))) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "src and dst formats differ for color"); | |
| return; | |
| } | |
| bool is_dst_signed_int = GLES2Util::IsSignedIntegerFormat(dst_format); | |
| bool is_dst_unsigned_int = GLES2Util::IsUnsignedIntegerFormat(dst_format); | |
| DCHECK(!is_dst_signed_int || !is_dst_unsigned_int); | |
| if (is_src_signed_int != is_dst_signed_int || | |
| is_src_unsigned_int != is_dst_unsigned_int) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "incompatible src/dst color formats"); | |
| return; | |
| } | |
| // Check whether draw buffers have identical color image with read buffer | |
| if (is_feedback_loop == FeedbackLoopUnknown) { | |
| GLenum attachment = static_cast<GLenum>(GL_COLOR_ATTACHMENT0 + ii); | |
| DCHECK(draw_framebuffer); | |
| const Framebuffer::Attachment* draw_buffer = | |
| draw_framebuffer->GetAttachment(attachment); | |
| if (!draw_buffer || !read_buffer) { | |
| continue; | |
| } | |
| if (draw_buffer->IsSameAttachment(read_buffer)) { | |
| is_feedback_loop = FeedbackLoopTrue; | |
| break; | |
| } | |
| } | |
| } | |
| if (draw_framebuffer && !draw_buffer_has_image) | |
| mask_blit &= ~GL_COLOR_BUFFER_BIT; | |
| } | |
| if (is_feedback_loop == FeedbackLoopTrue) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "source buffer and destination buffers are identical"); | |
| return; | |
| } | |
| if (read_framebuffer_miss_image == true) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "The designated attachment point(s) in read framebuffer miss image"); | |
| return; | |
| } | |
| if ((mask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) != 0) { | |
| if (filter != GL_NEAREST) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "invalid filter for depth/stencil"); | |
| return; | |
| } | |
| } | |
| mask = mask_blit; | |
| if (!mask) | |
| return; | |
| if (((mask & GL_DEPTH_BUFFER_BIT) != 0 && | |
| (GetBoundFramebufferDepthFormat(GL_READ_FRAMEBUFFER) != | |
| GetBoundFramebufferDepthFormat(GL_DRAW_FRAMEBUFFER))) || | |
| ((mask & GL_STENCIL_BUFFER_BIT) != 0 && | |
| ((GetBoundFramebufferStencilFormat(GL_READ_FRAMEBUFFER) != | |
| GetBoundFramebufferStencilFormat(GL_DRAW_FRAMEBUFFER))))) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "src and dst formats differ for depth/stencil"); | |
| return; | |
| } | |
| base::CheckedNumeric<GLint> src_width_temp = srcX1; | |
| src_width_temp -= srcX0; | |
| base::CheckedNumeric<GLint> src_height_temp = srcY1; | |
| src_height_temp -= srcY0; | |
| base::CheckedNumeric<GLint> dst_width_temp = dstX1; | |
| dst_width_temp -= dstX0; | |
| base::CheckedNumeric<GLint> dst_height_temp = dstY1; | |
| dst_height_temp -= dstY0; | |
| if (!src_width_temp.Abs().IsValid() || !src_height_temp.Abs().IsValid() || | |
| !dst_width_temp.Abs().IsValid() || !dst_height_temp.Abs().IsValid()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name, | |
| "the width or height of src or dst region overflowed"); | |
| return; | |
| } | |
| if (workarounds().adjust_src_dst_region_for_blitframebuffer) { | |
| gfx::Size read_size = GetBoundReadFramebufferSize(); | |
| GLint src_x = srcX1 > srcX0 ? srcX0 : srcX1; | |
| GLint src_y = srcY1 > srcY0 ? srcY0 : srcY1; | |
| unsigned int src_width = base::checked_cast<unsigned int>( | |
| src_width_temp.Abs().ValueOrDefault(0)); | |
| unsigned int src_height = base::checked_cast<unsigned int>( | |
| src_height_temp.Abs().ValueOrDefault(0)); | |
| GLint dst_x = dstX1 > dstX0 ? dstX0 : dstX1; | |
| GLint dst_y = dstY1 > dstY0 ? dstY0 : dstY1; | |
| unsigned int dst_width = base::checked_cast<unsigned int>( | |
| dst_width_temp.Abs().ValueOrDefault(0)); | |
| unsigned int dst_height = base::checked_cast<unsigned int>( | |
| dst_height_temp.Abs().ValueOrDefault(0)); | |
| if (dst_width == 0 || src_width == 0 || dst_height == 0 || | |
| src_height == 0) { | |
| return; | |
| } | |
| gfx::Rect src_bounds(0, 0, read_size.width(), read_size.height()); | |
| gfx::Rect src_region(src_x, src_y, src_width, src_height); | |
| gfx::Rect dst_bounds(0, 0, draw_size.width(), draw_size.height()); | |
| gfx::Rect dst_region(dst_x, dst_y, dst_width, dst_height); | |
| if (gfx::IntersectRects(dst_bounds, dst_region).IsEmpty()) { | |
| return; | |
| } | |
| bool x_flipped = ((srcX1 > srcX0) && (dstX1 < dstX0)) || | |
| ((srcX1 < srcX0) && (dstX1 > dstX0)); | |
| bool y_flipped = ((srcY1 > srcY0) && (dstY1 < dstY0)) || | |
| ((srcY1 < srcY0) && (dstY1 > dstY0)); | |
| if (!dst_bounds.Contains(dst_region)) { | |
| // dst_region is not within dst_bounds. We want to adjust it to a | |
| // reasonable size. This is done by halving the dst_region until it is at | |
| // most twice the size of the framebuffer. We cut it in half instead | |
| // of arbitrarily shrinking it to fit so that we don't end up with | |
| // non-power-of-two scale factors which could mess up pixel interpolation. | |
| // Naively clipping the dst rect and then proportionally sizing the | |
| // src rect yields incorrect results. | |
| unsigned int dst_x_halvings = 0; | |
| unsigned int dst_y_halvings = 0; | |
| int dst_origin_x = dst_x; | |
| int dst_origin_y = dst_y; | |
| int dst_clipped_width = dst_region.width(); | |
| while (dst_clipped_width > 2 * dst_bounds.width()) { | |
| dst_clipped_width = dst_clipped_width >> 1; | |
| dst_x_halvings++; | |
| } | |
| int dst_clipped_height = dst_region.height(); | |
| while (dst_clipped_height > 2 * dst_bounds.height()) { | |
| dst_clipped_height = dst_clipped_height >> 1; | |
| dst_y_halvings++; | |
| } | |
| // Before this block, we check that the two rectangles intersect. | |
| // Now, compute the location of a new region origin such that we use the | |
| // scaled dimensions but the new region has the same intersection as the | |
| // original region. | |
| int left = dst_region.x(); | |
| int right = dst_region.right(); | |
| int top = dst_region.y(); | |
| int bottom = dst_region.bottom(); | |
| if (left >= 0 && left < dst_bounds.width()) { | |
| // Left edge is in-bounds | |
| dst_origin_x = dst_x; | |
| } else if (right > 0 && right <= dst_bounds.width()) { | |
| // Right edge is in-bounds | |
| dst_origin_x = right - dst_clipped_width; | |
| } else { | |
| // Region completely spans bounds | |
| dst_origin_x = dst_x; | |
| } | |
| if (top >= 0 && top < dst_bounds.height()) { | |
| // Top edge is in-bounds | |
| dst_origin_y = dst_y; | |
| } else if (bottom > 0 && bottom <= dst_bounds.height()) { | |
| // Bottom edge is in-bounds | |
| dst_origin_y = bottom - dst_clipped_height; | |
| } else { | |
| // Region completely spans bounds | |
| dst_origin_y = dst_y; | |
| } | |
| dst_region.SetRect(dst_origin_x, dst_origin_y, dst_clipped_width, | |
| dst_clipped_height); | |
| // Offsets from the bottom left corner of the original region to | |
| // the bottom left corner of the clipped region. | |
| // This value (after it is scaled) is the respective offset we will apply | |
| // to the src origin. | |
| base::CheckedNumeric<unsigned int> checked_xoffset(dst_region.x() - | |
| dst_x); | |
| base::CheckedNumeric<unsigned int> checked_yoffset(dst_region.y() - | |
| dst_y); | |
| // if X/Y is reversed, use the top/right out-of-bounds region to compute | |
| // the origin offset instead of the left/bottom out-of-bounds region | |
| if (x_flipped) { | |
| checked_xoffset = (dst_x + dst_width - dst_region.right()); | |
| } | |
| if (y_flipped) { | |
| checked_yoffset = (dst_y + dst_height - dst_region.bottom()); | |
| } | |
| // These offsets should never overflow. | |
| unsigned int xoffset, yoffset; | |
| if (!checked_xoffset.AssignIfValid(&xoffset) || | |
| !checked_yoffset.AssignIfValid(&yoffset)) { | |
| NOTREACHED(); | |
| } | |
| // Adjust the src region by the same factor | |
| src_region.SetRect(src_x + (xoffset >> dst_x_halvings), | |
| src_y + (yoffset >> dst_y_halvings), | |
| src_region.width() >> dst_x_halvings, | |
| src_region.height() >> dst_y_halvings); | |
| // If the src was scaled to 0, set it to 1 so the src is non-empty. | |
| if (src_region.width() == 0) { | |
| src_region.set_width(1); | |
| } | |
| if (src_region.height() == 0) { | |
| src_region.set_height(1); | |
| } | |
| } | |
| if (!src_bounds.Contains(src_region)) { | |
| // If pixels lying outside the read framebuffer, adjust src region | |
| // and dst region to appropriate in-bounds regions respectively. | |
| src_region.Intersect(src_bounds); | |
| GLuint src_real_width = src_region.width(); | |
| GLuint src_real_height = src_region.height(); | |
| GLuint xoffset = src_region.x() - src_x; | |
| GLuint yoffset = src_region.y() - src_y; | |
| // if X/Y is reversed, use the top/right out-of-bounds region for mapping | |
| // to dst region, instead of left/bottom out-of-bounds region for mapping. | |
| if (x_flipped) { | |
| xoffset = src_x + src_width - src_region.x() - src_region.width(); | |
| } | |
| if (y_flipped) { | |
| yoffset = src_y + src_height - src_region.y() - src_region.height(); | |
| } | |
| GLfloat dst_mapping_width = | |
| static_cast<GLfloat>(src_real_width) * dst_width / src_width; | |
| GLfloat dst_mapping_height = | |
| static_cast<GLfloat>(src_real_height) * dst_height / src_height; | |
| GLfloat dst_mapping_xoffset = | |
| static_cast<GLfloat>(xoffset) * dst_width / src_width; | |
| GLfloat dst_mapping_yoffset = | |
| static_cast<GLfloat>(yoffset) * dst_height / src_height; | |
| GLuint dst_mapping_x0 = | |
| std::round(dst_x + dst_mapping_xoffset); | |
| GLuint dst_mapping_y0 = | |
| std::round(dst_y + dst_mapping_yoffset); | |
| GLuint dst_mapping_x1 = | |
| std::round(dst_x + dst_mapping_xoffset + dst_mapping_width); | |
| GLuint dst_mapping_y1 = | |
| std::round(dst_y + dst_mapping_yoffset + dst_mapping_height); | |
| dst_region.SetRect(dst_mapping_x0, dst_mapping_y0, | |
| dst_mapping_x1 - dst_mapping_x0, | |
| dst_mapping_y1 - dst_mapping_y0); | |
| } | |
| // Set the src and dst endpoints. If they were previously flipped, | |
| // set them as flipped. | |
| srcX0 = srcX0 < srcX1 ? src_region.x() : src_region.right(); | |
| srcY0 = srcY0 < srcY1 ? src_region.y() : src_region.bottom(); | |
| srcX1 = srcX0 < srcX1 ? src_region.right() : src_region.x(); | |
| srcY1 = srcY0 < srcY1 ? src_region.bottom() : src_region.y(); | |
| dstX0 = dstX0 < dstX1 ? dst_region.x() : dst_region.right(); | |
| dstY0 = dstY0 < dstY1 ? dst_region.y() : dst_region.bottom(); | |
| dstX1 = dstX0 < dstX1 ? dst_region.right() : dst_region.x(); | |
| dstY1 = dstY0 < dstY1 ? dst_region.bottom() : dst_region.y(); | |
| } | |
| bool enable_srgb = | |
| (read_buffer_has_srgb || draw_buffers_has_srgb) && | |
| ((mask & GL_COLOR_BUFFER_BIT) != 0); | |
| if (enable_srgb && feature_info_->feature_flags().ext_srgb_write_control) { | |
| state_.EnableDisableFramebufferSRGB(enable_srgb); | |
| } | |
| if (workarounds().finish_before_blit_framebuffer_multi_attachment && | |
| draw_framebuffer) { | |
| int color_attachment_count = 0; | |
| bool need_finish = false; | |
| for (uint32_t i = 0; i < group_->max_color_attachments(); ++i) { | |
| const Framebuffer::Attachment* attachment = | |
| draw_framebuffer->GetAttachment(GL_COLOR_ATTACHMENT0 + i); | |
| if (attachment) { | |
| ++color_attachment_count; | |
| if (color_attachment_count > 1) { | |
| need_finish = true; | |
| break; | |
| } | |
| if (attachment->IsTextureAttachment()) { | |
| GLuint client_id = attachment->object_name(); | |
| const TextureRef* texture_ref = | |
| texture_manager()->GetTexture(client_id); | |
| if (texture_ref && texture_ref->texture()->base_level() > 0) { | |
| need_finish = true; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| if (need_finish) { | |
| api()->glFinishFn(); | |
| } | |
| } | |
| api()->glBlitFramebufferFn(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, | |
| dstY1, mask, filter); | |
| } | |
| void GLES2DecoderImpl::UnbindTexture(TextureRef* texture_ref, | |
| bool supports_separate_framebuffer_binds) { | |
| Texture* texture = texture_ref->texture(); | |
| if (texture->IsAttachedToFramebuffer()) { | |
| framebuffer_state_.clear_state_dirty = true; | |
| } | |
| // Unbind texture_ref from texture_ref units. | |
| state_.UnbindTexture(texture_ref); | |
| // Unbind from current framebuffers. | |
| if (supports_separate_framebuffer_binds) { | |
| if (framebuffer_state_.bound_read_framebuffer.get()) { | |
| framebuffer_state_.bound_read_framebuffer->UnbindTexture( | |
| GL_READ_FRAMEBUFFER, texture_ref); | |
| } | |
| if (framebuffer_state_.bound_draw_framebuffer.get()) { | |
| framebuffer_state_.bound_draw_framebuffer->UnbindTexture( | |
| GL_DRAW_FRAMEBUFFER, texture_ref); | |
| } | |
| } else { | |
| if (framebuffer_state_.bound_draw_framebuffer.get()) { | |
| framebuffer_state_.bound_draw_framebuffer->UnbindTexture(GL_FRAMEBUFFER, | |
| texture_ref); | |
| } | |
| } | |
| } | |
| void GLES2DecoderImpl::EnsureRenderbufferBound() { | |
| if (!state_.bound_renderbuffer_valid) { | |
| state_.bound_renderbuffer_valid = true; | |
| api()->glBindRenderbufferEXTFn(GL_RENDERBUFFER, | |
| state_.bound_renderbuffer.get() | |
| ? state_.bound_renderbuffer->service_id() | |
| : 0); | |
| } | |
| } | |
| void GLES2DecoderImpl::RenderbufferStorageMultisampleWithWorkaround( | |
| GLenum target, | |
| GLsizei samples, | |
| GLenum internal_format, | |
| GLsizei width, | |
| GLsizei height, | |
| ForcedMultisampleMode mode) { | |
| ScopedBufferReattacher reattacher(this, state_.bound_renderbuffer.get()); | |
| RegenerateRenderbufferIfNeeded(state_.bound_renderbuffer.get()); | |
| EnsureRenderbufferBound(); | |
| RenderbufferStorageMultisampleHelper(target, samples, internal_format, width, | |
| height, mode); | |
| } | |
| void GLES2DecoderImpl::RenderbufferStorageMultisampleHelper( | |
| GLenum target, | |
| GLsizei samples, | |
| GLenum internal_format, | |
| GLsizei width, | |
| GLsizei height, | |
| ForcedMultisampleMode mode) { | |
| if (samples == 0) { | |
| api()->glRenderbufferStorageEXTFn(target, internal_format, width, height); | |
| return; | |
| } | |
| // TODO(sievers): This could be resolved at the GL binding level, but the | |
| // binding process is currently a bit too 'brute force'. | |
| // Note that when this is called via the | |
| // RenderbufferStorageMultisampleEXT handler, | |
| // kForceExtMultisampledRenderToTexture is passed in order to | |
| // prevent the core ES 3.0 multisampling code path from being used. | |
| if (mode == kForceExtMultisampledRenderToTexture) { | |
| api()->glRenderbufferStorageMultisampleEXTFn( | |
| target, samples, internal_format, width, height); | |
| } else { | |
| api()->glRenderbufferStorageMultisampleFn(target, samples, internal_format, | |
| width, height); | |
| } | |
| } | |
| void GLES2DecoderImpl::RenderbufferStorageMultisampleHelperAMD( | |
| GLenum target, | |
| GLsizei samples, | |
| GLsizei storageSamples, | |
| GLenum internal_format, | |
| GLsizei width, | |
| GLsizei height, | |
| ForcedMultisampleMode mode) { | |
| api()->glRenderbufferStorageMultisampleAdvancedAMDFn( | |
| target, samples, storageSamples, internal_format, width, height); | |
| } | |
| bool GLES2DecoderImpl::RegenerateRenderbufferIfNeeded( | |
| Renderbuffer* renderbuffer) { | |
| if (!renderbuffer->RegenerateAndBindBackingObjectIfNeeded(this, | |
| workarounds())) { | |
| return false; | |
| } | |
| if (renderbuffer != state_.bound_renderbuffer.get()) { | |
| // The renderbuffer bound in the driver has changed to the new | |
| // renderbuffer->service_id(). If that isn't state_.bound_renderbuffer, | |
| // then state_.bound_renderbuffer is no longer bound in the driver. | |
| state_.bound_renderbuffer_valid = false; | |
| } | |
| return true; | |
| } | |
| bool GLES2DecoderImpl::ValidateRenderbufferStorageMultisample( | |
| GLsizei samples, | |
| GLenum internalformat, | |
| GLsizei width, | |
| GLsizei height) { | |
| // Must check against the internal format's maximum number of samples | |
| // first in order to generate the correct INVALID_OPERATION rather than | |
| // INVALID_VALUE, below. | |
| if (feature_info_->IsES3Capable() && | |
| !GLES2Util::IsIntegerFormat(internalformat)) { | |
| std::vector<GLint> sample_counts; | |
| GLsizei num_sample_counts = InternalFormatSampleCountsHelper( | |
| GL_RENDERBUFFER, internalformat, &sample_counts); | |
| // SwiftShader reports 0 samples for GL_DEPTH24_STENCIL8; be robust to this. | |
| if (num_sample_counts > 0) { | |
| if (samples > sample_counts[0]) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, | |
| "glRenderbufferStorageMultisample", | |
| "samples out of range for internalformat"); | |
| return false; | |
| } | |
| } | |
| } | |
| if (samples > renderbuffer_manager()->max_samples()) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, | |
| "glRenderbufferStorageMultisample", "samples too large"); | |
| return false; | |
| } | |
| if (width > renderbuffer_manager()->max_renderbuffer_size() || | |
| height > renderbuffer_manager()->max_renderbuffer_size()) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, | |
| "glRenderbufferStorageMultisample", "dimensions too large"); | |
| return false; | |
| } | |
| uint32_t estimated_size = 0; | |
| if (!renderbuffer_manager()->ComputeEstimatedRenderbufferSize( | |
| width, height, samples, internalformat, &estimated_size)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_OUT_OF_MEMORY, | |
| "glRenderbufferStorageMultisample", "dimensions too large"); | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool GLES2DecoderImpl::ValidateRenderbufferStorageMultisampleAMD( | |
| GLsizei samples, | |
| GLsizei storageSamples, | |
| GLenum internalformat, | |
| GLsizei width, | |
| GLsizei height) { | |
| if (samples > renderbuffer_manager()->max_samples()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glRenderbufferStorageMultisample", | |
| "samples too large"); | |
| return false; | |
| } | |
| if (width > renderbuffer_manager()->max_renderbuffer_size() || | |
| height > renderbuffer_manager()->max_renderbuffer_size()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glRenderbufferStorageMultisample", | |
| "dimensions too large"); | |
| return false; | |
| } | |
| uint32_t estimated_size = 0; | |
| if (!renderbuffer_manager()->ComputeEstimatedRenderbufferSize( | |
| width, height, samples, internalformat, &estimated_size)) { | |
| LOCAL_SET_GL_ERROR(GL_OUT_OF_MEMORY, "glRenderbufferStorageMultisample", | |
| "dimensions too large"); | |
| return false; | |
| } | |
| return true; | |
| } | |
| void GLES2DecoderImpl::DoRenderbufferStorageMultisampleCHROMIUM( | |
| GLenum target, GLsizei samples, GLenum internalformat, | |
| GLsizei width, GLsizei height) { | |
| Renderbuffer* renderbuffer = GetRenderbufferInfoForTarget(GL_RENDERBUFFER); | |
| if (!renderbuffer) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, | |
| "glRenderbufferStorageMultisampleCHROMIUM", | |
| "no renderbuffer bound"); | |
| return; | |
| } | |
| if (!ValidateRenderbufferStorageMultisample( | |
| samples, internalformat, width, height)) { | |
| return; | |
| } | |
| GLenum impl_format = | |
| renderbuffer_manager()->InternalRenderbufferFormatToImplFormat( | |
| internalformat); | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER( | |
| "glRenderbufferStorageMultisampleCHROMIUM"); | |
| RenderbufferStorageMultisampleWithWorkaround(target, samples, impl_format, | |
| width, height, kDoNotForce); | |
| GLenum error = | |
| LOCAL_PEEK_GL_ERROR("glRenderbufferStorageMultisampleCHROMIUM"); | |
| if (error == GL_NO_ERROR) { | |
| renderbuffer_manager()->SetInfoAndInvalidate(renderbuffer, samples, | |
| internalformat, width, height); | |
| } else { | |
| renderbuffer_manager()->SetAllocationFailed(renderbuffer); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoRenderbufferStorageMultisampleAdvancedAMD( | |
| GLenum target, | |
| GLsizei samples, | |
| GLsizei storageSamples, | |
| GLenum internalformat, | |
| GLsizei width, | |
| GLsizei height) { | |
| Renderbuffer* renderbuffer = GetRenderbufferInfoForTarget(GL_RENDERBUFFER); | |
| if (!renderbuffer) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, | |
| "glRenderbufferStorageMultisampleAdvancedAMD", | |
| "no renderbuffer bound"); | |
| return; | |
| } | |
| if (!ValidateRenderbufferStorageMultisampleAMD( | |
| samples, storageSamples, internalformat, width, height)) { | |
| return; | |
| } | |
| GLenum impl_format = | |
| renderbuffer_manager()->InternalRenderbufferFormatToImplFormat( | |
| internalformat); | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER( | |
| "glRenderbufferStorageMultisampleAdvancedAMD"); | |
| RenderbufferStorageMultisampleHelperAMD( | |
| target, samples, storageSamples, impl_format, width, height, kDoNotForce); | |
| GLenum error = | |
| LOCAL_PEEK_GL_ERROR("glRenderbufferStorageMultisampleAdvancedAMD"); | |
| if (error == GL_NO_ERROR) { | |
| renderbuffer_manager()->SetInfoAndInvalidate(renderbuffer, samples, | |
| internalformat, width, height); | |
| } | |
| } | |
| // This is the handler for multisampled_render_to_texture extensions. | |
| void GLES2DecoderImpl::DoRenderbufferStorageMultisampleEXT( | |
| GLenum target, GLsizei samples, GLenum internalformat, | |
| GLsizei width, GLsizei height) { | |
| Renderbuffer* renderbuffer = GetRenderbufferInfoForTarget(GL_RENDERBUFFER); | |
| if (!renderbuffer) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, | |
| "glRenderbufferStorageMultisampleEXT", | |
| "no renderbuffer bound"); | |
| return; | |
| } | |
| if (!ValidateRenderbufferStorageMultisample( | |
| samples, internalformat, width, height)) { | |
| return; | |
| } | |
| GLenum impl_format = | |
| renderbuffer_manager()->InternalRenderbufferFormatToImplFormat( | |
| internalformat); | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER("glRenderbufferStorageMultisampleEXT"); | |
| RenderbufferStorageMultisampleWithWorkaround( | |
| target, samples, impl_format, width, height, | |
| kForceExtMultisampledRenderToTexture); | |
| GLenum error = LOCAL_PEEK_GL_ERROR("glRenderbufferStorageMultisampleEXT"); | |
| if (error == GL_NO_ERROR) { | |
| renderbuffer_manager()->SetInfoAndInvalidate(renderbuffer, samples, | |
| internalformat, width, height); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoReleaseShaderCompiler() { | |
| DestroyShaderTranslator(); | |
| } | |
| void GLES2DecoderImpl::DoRenderbufferStorage( | |
| GLenum target, GLenum internalformat, GLsizei width, GLsizei height) { | |
| Renderbuffer* renderbuffer = | |
| GetRenderbufferInfoForTarget(GL_RENDERBUFFER); | |
| if (!renderbuffer) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, | |
| "glRenderbufferStorage", "no renderbuffer bound"); | |
| return; | |
| } | |
| if (width > renderbuffer_manager()->max_renderbuffer_size() || | |
| height > renderbuffer_manager()->max_renderbuffer_size()) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glRenderbufferStorage", "dimensions too large"); | |
| return; | |
| } | |
| uint32_t estimated_size = 0; | |
| if (!renderbuffer_manager()->ComputeEstimatedRenderbufferSize( | |
| width, height, 1, internalformat, &estimated_size)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_OUT_OF_MEMORY, "glRenderbufferStorage", "dimensions too large"); | |
| return; | |
| } | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER("glRenderbufferStorage"); | |
| RenderbufferStorageMultisampleWithWorkaround( | |
| target, 0, | |
| renderbuffer_manager()->InternalRenderbufferFormatToImplFormat( | |
| internalformat), | |
| width, height, kDoNotForce); | |
| GLenum error = LOCAL_PEEK_GL_ERROR("glRenderbufferStorage"); | |
| if (error == GL_NO_ERROR) { | |
| renderbuffer_manager()->SetInfoAndInvalidate(renderbuffer, 0, | |
| internalformat, width, height); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoLineWidth(GLfloat width) { | |
| api()->glLineWidthFn( | |
| std::clamp(width, line_width_range_[0], line_width_range_[1])); | |
| } | |
| void GLES2DecoderImpl::DoLinkProgram(GLuint program_id) { | |
| TRACE_EVENT0("gpu", "GLES2DecoderImpl::DoLinkProgram"); | |
| Program* program = GetProgramInfoNotShader( | |
| program_id, "glLinkProgram"); | |
| if (!program) { | |
| return; | |
| } | |
| if (program->IsActiveForTransformFeedback()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glLinkProgram", | |
| "program is active for transform feedback"); | |
| return; | |
| } | |
| LogClientServiceForInfo(program, program_id, "glLinkProgram"); | |
| if (program->Link(shader_manager(), | |
| client())) { | |
| if (features().webgl_multi_draw) | |
| program_manager()->UpdateDrawIDUniformLocation(program); | |
| if (features().webgl_draw_instanced_base_vertex_base_instance || | |
| features().webgl_multi_draw_instanced_base_vertex_base_instance) { | |
| program_manager()->UpdateBaseVertexUniformLocation(program); | |
| program_manager()->UpdateBaseInstanceUniformLocation(program); | |
| } | |
| } | |
| // LinkProgram can be very slow. Exit command processing to allow for | |
| // context preemption and GPU watchdog checks. | |
| ExitCommandProcessingEarly(); | |
| } | |
| void GLES2DecoderImpl::DoReadBuffer(GLenum src) { | |
| Framebuffer* framebuffer = GetFramebufferInfoForTarget(GL_READ_FRAMEBUFFER); | |
| if (framebuffer) { | |
| if (src == GL_BACK) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_ENUM, "glReadBuffer", | |
| "invalid src for a named framebuffer"); | |
| return; | |
| } | |
| framebuffer->set_read_buffer(src); | |
| } else { | |
| if (src != GL_NONE && src != GL_BACK) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_ENUM, "glReadBuffer", | |
| "invalid src for the default framebuffer"); | |
| return; | |
| } | |
| back_buffer_read_buffer_ = src; | |
| if (GetBackbufferServiceId() && src == GL_BACK) | |
| src = GL_COLOR_ATTACHMENT0; | |
| } | |
| api()->glReadBufferFn(src); | |
| } | |
| void GLES2DecoderImpl::DoSamplerParameterf( | |
| GLuint client_id, GLenum pname, GLfloat param) { | |
| Sampler* sampler = GetSampler(client_id); | |
| if (!sampler) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glSamplerParameterf", "unknown sampler"); | |
| return; | |
| } | |
| sampler_manager()->SetParameterf("glSamplerParameterf", error_state_.get(), | |
| sampler, pname, param); | |
| } | |
| void GLES2DecoderImpl::DoSamplerParameteri( | |
| GLuint client_id, GLenum pname, GLint param) { | |
| Sampler* sampler = GetSampler(client_id); | |
| if (!sampler) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glSamplerParameteri", "unknown sampler"); | |
| return; | |
| } | |
| sampler_manager()->SetParameteri("glSamplerParameteri", error_state_.get(), | |
| sampler, pname, param); | |
| } | |
| void GLES2DecoderImpl::DoSamplerParameterfv(GLuint client_id, | |
| GLenum pname, | |
| const volatile GLfloat* params) { | |
| DCHECK(params); | |
| Sampler* sampler = GetSampler(client_id); | |
| if (!sampler) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glSamplerParameterfv", "unknown sampler"); | |
| return; | |
| } | |
| sampler_manager()->SetParameterf("glSamplerParameterfv", error_state_.get(), | |
| sampler, pname, params[0]); | |
| } | |
| void GLES2DecoderImpl::DoSamplerParameteriv(GLuint client_id, | |
| GLenum pname, | |
| const volatile GLint* params) { | |
| DCHECK(params); | |
| Sampler* sampler = GetSampler(client_id); | |
| if (!sampler) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glSamplerParameteriv", "unknown sampler"); | |
| return; | |
| } | |
| sampler_manager()->SetParameteri("glSamplerParameteriv", error_state_.get(), | |
| sampler, pname, params[0]); | |
| } | |
| bool GLES2DecoderImpl::CheckTexParameterBaseLevel(TextureRef* texture, | |
| GLenum pname, | |
| GLint param) { | |
| if (pname == GL_TEXTURE_BASE_LEVEL && | |
| workarounds().dont_change_base_level_for_npot_immutable_textures) { | |
| Texture* tex = texture->texture(); | |
| if (tex->base_level() != param && tex->target() == GL_TEXTURE_2D && | |
| tex->IsImmutable()) { | |
| GLsizei width = 0, height = 0, depth = 0; | |
| if (tex->GetLevelSize(tex->target(), 0, &width, &height, &depth) && | |
| (GLES2Util::IsNPOT(width) || GLES2Util::IsNPOT(height))) { | |
| MarkContextLost(error::kGuilty); | |
| group_->LoseContexts(error::kUnknown); | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| void GLES2DecoderImpl::DoTexParameterf( | |
| GLenum target, GLenum pname, GLfloat param) { | |
| TextureRef* texture = texture_manager()->GetTextureInfoForTarget( | |
| &state_, target); | |
| if (!texture) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glTexParameterf", "unknown texture"); | |
| return; | |
| } | |
| if (!CheckTexParameterBaseLevel(texture, pname, static_cast<GLint>(param))) { | |
| return; | |
| } | |
| texture_manager()->SetParameterf("glTexParameterf", error_state_.get(), | |
| texture, pname, param); | |
| } | |
| void GLES2DecoderImpl::DoTexParameteri( | |
| GLenum target, GLenum pname, GLint param) { | |
| TextureRef* texture = texture_manager()->GetTextureInfoForTarget( | |
| &state_, target); | |
| if (!texture) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glTexParameteri", "unknown texture"); | |
| return; | |
| } | |
| if (!CheckTexParameterBaseLevel(texture, pname, param)) { | |
| return; | |
| } | |
| texture_manager()->SetParameteri("glTexParameteri", error_state_.get(), | |
| texture, pname, param); | |
| } | |
| void GLES2DecoderImpl::DoTexParameterfv(GLenum target, | |
| GLenum pname, | |
| const volatile GLfloat* params) { | |
| TextureRef* texture = texture_manager()->GetTextureInfoForTarget( | |
| &state_, target); | |
| if (!texture) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glTexParameterfv", "unknown texture"); | |
| return; | |
| } | |
| if (!CheckTexParameterBaseLevel(texture, pname, | |
| static_cast<GLint>(*params))) { | |
| return; | |
| } | |
| texture_manager()->SetParameterf("glTexParameterfv", error_state_.get(), | |
| texture, pname, *params); | |
| } | |
| void GLES2DecoderImpl::DoTexParameteriv(GLenum target, | |
| GLenum pname, | |
| const volatile GLint* params) { | |
| TextureRef* texture = texture_manager()->GetTextureInfoForTarget( | |
| &state_, target); | |
| if (!texture) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glTexParameteriv", "unknown texture"); | |
| return; | |
| } | |
| if (!CheckTexParameterBaseLevel(texture, pname, *params)) { | |
| return; | |
| } | |
| texture_manager()->SetParameteri("glTexParameteriv", error_state_.get(), | |
| texture, pname, *params); | |
| } | |
| bool GLES2DecoderImpl::CheckCurrentProgram(const char* function_name) { | |
| if (!state_.current_program.get()) { | |
| // The program does not exist. | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, function_name, "no program in use"); | |
| return false; | |
| } | |
| if (!state_.current_program->InUse()) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, function_name, "program not linked"); | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool GLES2DecoderImpl::CheckCurrentProgramForUniform( | |
| GLint location, const char* function_name) { | |
| if (!CheckCurrentProgram(function_name)) { | |
| return false; | |
| } | |
| return !state_.current_program->IsInactiveUniformLocationByFakeLocation( | |
| location); | |
| } | |
| bool GLES2DecoderImpl::CheckDrawingFeedbackLoopsHelper( | |
| const Framebuffer::Attachment* attachment, | |
| TextureRef* texture_ref, | |
| const char* function_name) { | |
| if (attachment && attachment->IsTexture(texture_ref)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, function_name, | |
| "Source and destination textures of the draw are the same."); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool GLES2DecoderImpl::SupportsDrawBuffers() const { | |
| return feature_info_->IsWebGL1OrES2Context() ? | |
| feature_info_->feature_flags().ext_draw_buffers : true; | |
| } | |
| bool GLES2DecoderImpl::ValidateAndAdjustDrawBuffers(const char* func_name) { | |
| if (state_.GetEnabled(GL_RASTERIZER_DISCARD)) { | |
| return true; | |
| } | |
| if (!SupportsDrawBuffers()) { | |
| return true; | |
| } | |
| Framebuffer* framebuffer = framebuffer_state_.bound_draw_framebuffer.get(); | |
| if (!state_.current_program.get() || !framebuffer) { | |
| return true; | |
| } | |
| if (!state_.color_mask_red && !state_.color_mask_green && | |
| !state_.color_mask_blue && !state_.color_mask_alpha) { | |
| return true; | |
| } | |
| uint32_t fragment_output_type_mask = | |
| state_.current_program->fragment_output_type_mask(); | |
| uint32_t fragment_output_written_mask = | |
| state_.current_program->fragment_output_written_mask(); | |
| if (!framebuffer->ValidateAndAdjustDrawBuffers( | |
| fragment_output_type_mask, fragment_output_written_mask)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "buffer format and fragment output variable type incompatible"); | |
| return false; | |
| } | |
| return true; | |
| } | |
| void GLES2DecoderImpl::AdjustDrawBuffers() { | |
| if (!SupportsDrawBuffers()) { | |
| return; | |
| } | |
| Framebuffer* framebuffer = framebuffer_state_.bound_draw_framebuffer.get(); | |
| if (framebuffer) { | |
| framebuffer->AdjustDrawBuffers(); | |
| } | |
| } | |
| bool GLES2DecoderImpl::ValidateUniformBlockBackings(const char* func_name) { | |
| DCHECK(feature_info_->IsWebGL2OrES3Context()); | |
| if (!state_.current_program.get()) | |
| return true; | |
| int32_t max_index = -1; | |
| for (auto info : state_.current_program->uniform_block_size_info()) { | |
| int32_t index = static_cast<int32_t>(info.binding); | |
| if (index > max_index) | |
| max_index = index; | |
| } | |
| if (max_index < 0) | |
| return true; | |
| std::vector<GLsizeiptr> uniform_block_sizes(max_index + 1); | |
| for (int32_t ii = 0; ii <= max_index; ++ii) | |
| uniform_block_sizes[ii] = 0; | |
| for (auto info : state_.current_program->uniform_block_size_info()) { | |
| uint32_t index = info.binding; | |
| uniform_block_sizes[index] = std::max( | |
| uniform_block_sizes[index], static_cast<GLsizeiptr>(info.data_size)); | |
| } | |
| return buffer_manager()->RequestBuffersAccess( | |
| error_state_.get(), state_.indexed_uniform_buffer_bindings.get(), | |
| uniform_block_sizes, 1, func_name, "uniform buffers"); | |
| } | |
| bool GLES2DecoderImpl::CheckUniformForApiType(const Program::UniformInfo* info, | |
| const char* function_name, | |
| UniformApiType api_type) { | |
| DCHECK(info); | |
| if ((api_type & info->accepts_api_type) == UniformApiType::kUniformNone) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "wrong uniform function for type"); | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool GLES2DecoderImpl::PrepForSetUniformByLocation(GLint fake_location, | |
| const char* function_name, | |
| UniformApiType api_type, | |
| GLint* real_location, | |
| GLenum* type, | |
| GLsizei* count) { | |
| DCHECK(type); | |
| DCHECK(count); | |
| DCHECK(real_location); | |
| if (!CheckCurrentProgramForUniform(fake_location, function_name)) { | |
| return false; | |
| } | |
| GLint array_index = -1; | |
| const Program::UniformInfo* info = | |
| state_.current_program->GetUniformInfoByFakeLocation( | |
| fake_location, real_location, &array_index); | |
| if (!info) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, function_name, "unknown location"); | |
| return false; | |
| } | |
| if (!CheckUniformForApiType(info, function_name, api_type)) { | |
| return false; | |
| } | |
| if (*count > 1 && !info->is_array) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, function_name, "count > 1 for non-array"); | |
| return false; | |
| } | |
| *count = std::min(info->size - array_index, *count); | |
| if (*count <= 0) { | |
| return false; | |
| } | |
| *type = info->type; | |
| return true; | |
| } | |
| void GLES2DecoderImpl::DoUniform1i(GLint fake_location, GLint v0) { | |
| GLenum type = 0; | |
| GLsizei count = 1; | |
| GLint real_location = -1; | |
| if (!PrepForSetUniformByLocation(fake_location, "glUniform1i", | |
| UniformApiType::kUniform1i, &real_location, | |
| &type, &count)) { | |
| return; | |
| } | |
| if (!state_.current_program->SetSamplers( | |
| state_.texture_units.size(), fake_location, 1, &v0)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glUniform1i", "texture unit out of range"); | |
| return; | |
| } | |
| api()->glUniform1iFn(real_location, v0); | |
| } | |
| void GLES2DecoderImpl::DoUniform1iv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLint* values) { | |
| GLenum type = 0; | |
| GLint real_location = -1; | |
| if (!PrepForSetUniformByLocation(fake_location, "glUniform1iv", | |
| UniformApiType::kUniform1i, &real_location, | |
| &type, &count)) { | |
| return; | |
| } | |
| auto values_copy = std::make_unique<GLint[]>(count); | |
| GLint* safe_values = values_copy.get(); | |
| std::copy(values, UNSAFE_TODO(values + count), safe_values); | |
| if (!state_.current_program->SetSamplers(state_.texture_units.size(), | |
| fake_location, count, safe_values)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glUniform1iv", | |
| "texture unit out of range"); | |
| return; | |
| } | |
| api()->glUniform1ivFn(real_location, count, safe_values); | |
| } | |
| void GLES2DecoderImpl::DoUniform1uiv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLuint* value) { | |
| GLenum type = 0; | |
| GLint real_location = -1; | |
| if (!PrepForSetUniformByLocation(fake_location, "glUniform1uiv", | |
| UniformApiType::kUniform1ui, &real_location, | |
| &type, &count)) { | |
| return; | |
| } | |
| api()->glUniform1uivFn(real_location, count, | |
| const_cast<const GLuint*>(value)); | |
| } | |
| void GLES2DecoderImpl::DoUniform1fv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLfloat* value) { | |
| GLenum type = 0; | |
| GLint real_location = -1; | |
| if (!PrepForSetUniformByLocation(fake_location, "glUniform1fv", | |
| UniformApiType::kUniform1f, &real_location, | |
| &type, &count)) { | |
| return; | |
| } | |
| if (type == GL_BOOL) { | |
| auto temp = base::HeapArray<GLint>::Uninit(count); | |
| for (GLsizei ii = 0; ii < count; ++ii) { | |
| temp[ii] = static_cast<GLint>(UNSAFE_TODO(value[ii]) != 0.0f); | |
| } | |
| api()->glUniform1ivFn(real_location, count, temp.data()); | |
| } else { | |
| api()->glUniform1fvFn(real_location, count, | |
| const_cast<const GLfloat*>(value)); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoUniform2fv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLfloat* value) { | |
| GLenum type = 0; | |
| GLint real_location = -1; | |
| if (!PrepForSetUniformByLocation(fake_location, "glUniform2fv", | |
| UniformApiType::kUniform2f, &real_location, | |
| &type, &count)) { | |
| return; | |
| } | |
| if (type == GL_BOOL_VEC2) { | |
| GLsizei num_values = count * 2; | |
| auto temp = base::HeapArray<GLint>::Uninit(num_values); | |
| for (GLsizei ii = 0; ii < num_values; ++ii) { | |
| temp[ii] = static_cast<GLint>(UNSAFE_TODO(value[ii]) != 0.0f); | |
| } | |
| api()->glUniform2ivFn(real_location, count, temp.data()); | |
| } else { | |
| api()->glUniform2fvFn(real_location, count, | |
| const_cast<const GLfloat*>(value)); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoUniform3fv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLfloat* value) { | |
| GLenum type = 0; | |
| GLint real_location = -1; | |
| if (!PrepForSetUniformByLocation(fake_location, "glUniform3fv", | |
| UniformApiType::kUniform3f, &real_location, | |
| &type, &count)) { | |
| return; | |
| } | |
| if (type == GL_BOOL_VEC3) { | |
| GLsizei num_values = count * 3; | |
| auto temp = base::HeapArray<GLint>::Uninit(num_values); | |
| for (GLsizei ii = 0; ii < num_values; ++ii) { | |
| temp[ii] = static_cast<GLint>(UNSAFE_TODO(value[ii]) != 0.0f); | |
| } | |
| api()->glUniform3ivFn(real_location, count, temp.data()); | |
| } else { | |
| api()->glUniform3fvFn(real_location, count, | |
| const_cast<const GLfloat*>(value)); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoUniform4fv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLfloat* value) { | |
| GLenum type = 0; | |
| GLint real_location = -1; | |
| if (!PrepForSetUniformByLocation(fake_location, "glUniform4fv", | |
| UniformApiType::kUniform4f, &real_location, | |
| &type, &count)) { | |
| return; | |
| } | |
| if (type == GL_BOOL_VEC4) { | |
| GLsizei num_values = count * 4; | |
| auto temp = base::HeapArray<GLint>::Uninit(num_values); | |
| for (GLsizei ii = 0; ii < num_values; ++ii) { | |
| temp[ii] = static_cast<GLint>(UNSAFE_TODO(value[ii]) != 0.0f); | |
| } | |
| api()->glUniform4ivFn(real_location, count, temp.data()); | |
| } else { | |
| api()->glUniform4fvFn(real_location, count, | |
| const_cast<const GLfloat*>(value)); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoUniform2iv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLint* value) { | |
| GLenum type = 0; | |
| GLint real_location = -1; | |
| if (!PrepForSetUniformByLocation(fake_location, "glUniform2iv", | |
| UniformApiType::kUniform2i, &real_location, | |
| &type, &count)) { | |
| return; | |
| } | |
| api()->glUniform2ivFn(real_location, count, const_cast<const GLint*>(value)); | |
| } | |
| void GLES2DecoderImpl::DoUniform2uiv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLuint* value) { | |
| GLenum type = 0; | |
| GLint real_location = -1; | |
| if (!PrepForSetUniformByLocation(fake_location, "glUniform2uiv", | |
| UniformApiType::kUniform2ui, &real_location, | |
| &type, &count)) { | |
| return; | |
| } | |
| api()->glUniform2uivFn(real_location, count, | |
| const_cast<const GLuint*>(value)); | |
| } | |
| void GLES2DecoderImpl::DoUniform3iv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLint* value) { | |
| GLenum type = 0; | |
| GLint real_location = -1; | |
| if (!PrepForSetUniformByLocation(fake_location, "glUniform3iv", | |
| UniformApiType::kUniform3i, &real_location, | |
| &type, &count)) { | |
| return; | |
| } | |
| api()->glUniform3ivFn(real_location, count, const_cast<const GLint*>(value)); | |
| } | |
| void GLES2DecoderImpl::DoUniform3uiv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLuint* value) { | |
| GLenum type = 0; | |
| GLint real_location = -1; | |
| if (!PrepForSetUniformByLocation(fake_location, "glUniform3uiv", | |
| UniformApiType::kUniform3ui, &real_location, | |
| &type, &count)) { | |
| return; | |
| } | |
| api()->glUniform3uivFn(real_location, count, | |
| const_cast<const GLuint*>(value)); | |
| } | |
| void GLES2DecoderImpl::DoUniform4iv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLint* value) { | |
| GLenum type = 0; | |
| GLint real_location = -1; | |
| if (!PrepForSetUniformByLocation(fake_location, "glUniform4iv", | |
| UniformApiType::kUniform4i, &real_location, | |
| &type, &count)) { | |
| return; | |
| } | |
| api()->glUniform4ivFn(real_location, count, const_cast<const GLint*>(value)); | |
| } | |
| void GLES2DecoderImpl::DoUniform4uiv(GLint fake_location, | |
| GLsizei count, | |
| const volatile GLuint* value) { | |
| GLenum type = 0; | |
| GLint real_location = -1; | |
| if (!PrepForSetUniformByLocation(fake_location, "glUniform4uiv", | |
| UniformApiType::kUniform4ui, &real_location, | |
| &type, &count)) { | |
| return; | |
| } | |
| api()->glUniform4uivFn(real_location, count, | |
| const_cast<const GLuint*>(value)); | |
| } | |
| void GLES2DecoderImpl::DoUniformMatrix2fv(GLint fake_location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const volatile GLfloat* value) { | |
| GLenum type = 0; | |
| GLint real_location = -1; | |
| if (transpose && !feature_info_->IsWebGL2OrES3Context()) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glUniformMatrix2fv", "transpose not FALSE"); | |
| return; | |
| } | |
| if (!PrepForSetUniformByLocation(fake_location, "glUniformMatrix2fv", | |
| UniformApiType::kUniformMatrix2f, | |
| &real_location, &type, &count)) { | |
| return; | |
| } | |
| api()->glUniformMatrix2fvFn(real_location, count, transpose, | |
| const_cast<const GLfloat*>(value)); | |
| } | |
| void GLES2DecoderImpl::DoUniformMatrix3fv(GLint fake_location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const volatile GLfloat* value) { | |
| GLenum type = 0; | |
| GLint real_location = -1; | |
| if (transpose && !feature_info_->IsWebGL2OrES3Context()) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glUniformMatrix3fv", "transpose not FALSE"); | |
| return; | |
| } | |
| if (!PrepForSetUniformByLocation(fake_location, "glUniformMatrix3fv", | |
| UniformApiType::kUniformMatrix3f, | |
| &real_location, &type, &count)) { | |
| return; | |
| } | |
| api()->glUniformMatrix3fvFn(real_location, count, transpose, | |
| const_cast<const GLfloat*>(value)); | |
| } | |
| void GLES2DecoderImpl::DoUniformMatrix4fv(GLint fake_location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const volatile GLfloat* value) { | |
| GLenum type = 0; | |
| GLint real_location = -1; | |
| if (transpose && !feature_info_->IsWebGL2OrES3Context()) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glUniformMatrix4fv", "transpose not FALSE"); | |
| return; | |
| } | |
| if (!PrepForSetUniformByLocation(fake_location, "glUniformMatrix4fv", | |
| UniformApiType::kUniformMatrix4f, | |
| &real_location, &type, &count)) { | |
| return; | |
| } | |
| api()->glUniformMatrix4fvFn(real_location, count, transpose, | |
| const_cast<const GLfloat*>(value)); | |
| } | |
| void GLES2DecoderImpl::DoUniformMatrix2x3fv(GLint fake_location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const volatile GLfloat* value) { | |
| GLenum type = 0; | |
| GLint real_location = -1; | |
| if (!PrepForSetUniformByLocation(fake_location, "glUniformMatrix2x3fv", | |
| UniformApiType::kUniformMatrix2x3f, | |
| &real_location, &type, &count)) { | |
| return; | |
| } | |
| api()->glUniformMatrix2x3fvFn(real_location, count, transpose, | |
| const_cast<const GLfloat*>(value)); | |
| } | |
| void GLES2DecoderImpl::DoUniformMatrix2x4fv(GLint fake_location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const volatile GLfloat* value) { | |
| GLenum type = 0; | |
| GLint real_location = -1; | |
| if (!PrepForSetUniformByLocation(fake_location, "glUniformMatrix2x4fv", | |
| UniformApiType::kUniformMatrix2x4f, | |
| &real_location, &type, &count)) { | |
| return; | |
| } | |
| api()->glUniformMatrix2x4fvFn(real_location, count, transpose, | |
| const_cast<const GLfloat*>(value)); | |
| } | |
| void GLES2DecoderImpl::DoUniformMatrix3x2fv(GLint fake_location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const volatile GLfloat* value) { | |
| GLenum type = 0; | |
| GLint real_location = -1; | |
| if (!PrepForSetUniformByLocation(fake_location, "glUniformMatrix3x2fv", | |
| UniformApiType::kUniformMatrix3x2f, | |
| &real_location, &type, &count)) { | |
| return; | |
| } | |
| api()->glUniformMatrix3x2fvFn(real_location, count, transpose, | |
| const_cast<const GLfloat*>(value)); | |
| } | |
| void GLES2DecoderImpl::DoUniformMatrix3x4fv(GLint fake_location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const volatile GLfloat* value) { | |
| GLenum type = 0; | |
| GLint real_location = -1; | |
| if (!PrepForSetUniformByLocation(fake_location, "glUniformMatrix3x4fv", | |
| UniformApiType::kUniformMatrix3x4f, | |
| &real_location, &type, &count)) { | |
| return; | |
| } | |
| api()->glUniformMatrix3x4fvFn(real_location, count, transpose, | |
| const_cast<const GLfloat*>(value)); | |
| } | |
| void GLES2DecoderImpl::DoUniformMatrix4x2fv(GLint fake_location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const volatile GLfloat* value) { | |
| GLenum type = 0; | |
| GLint real_location = -1; | |
| if (!PrepForSetUniformByLocation(fake_location, "glUniformMatrix4x2fv", | |
| UniformApiType::kUniformMatrix4x2f, | |
| &real_location, &type, &count)) { | |
| return; | |
| } | |
| api()->glUniformMatrix4x2fvFn(real_location, count, transpose, | |
| const_cast<const GLfloat*>(value)); | |
| } | |
| void GLES2DecoderImpl::DoUniformMatrix4x3fv(GLint fake_location, | |
| GLsizei count, | |
| GLboolean transpose, | |
| const volatile GLfloat* value) { | |
| GLenum type = 0; | |
| GLint real_location = -1; | |
| if (!PrepForSetUniformByLocation(fake_location, "glUniformMatrix4x3fv", | |
| UniformApiType::kUniformMatrix4x3f, | |
| &real_location, &type, &count)) { | |
| return; | |
| } | |
| api()->glUniformMatrix4x3fvFn(real_location, count, transpose, | |
| const_cast<const GLfloat*>(value)); | |
| } | |
| void GLES2DecoderImpl::DoUseProgram(GLuint program_id) { | |
| const char* function_name = "glUseProgram"; | |
| GLuint service_id = 0; | |
| Program* program = nullptr; | |
| if (program_id) { | |
| program = GetProgramInfoNotShader(program_id, function_name); | |
| if (!program) { | |
| return; | |
| } | |
| if (!program->IsValid()) { | |
| // Program was not linked successfully. (ie, glLinkProgram) | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, function_name, "program not linked"); | |
| return; | |
| } | |
| service_id = program->service_id(); | |
| } | |
| if (state_.bound_transform_feedback.get() && | |
| state_.bound_transform_feedback->active() && | |
| !state_.bound_transform_feedback->paused()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "transformfeedback is active and not paused"); | |
| return; | |
| } | |
| if (program == state_.current_program.get()) | |
| return; | |
| if (state_.current_program.get()) { | |
| program_manager()->UnuseProgram(shader_manager(), | |
| state_.current_program.get()); | |
| } | |
| state_.current_program = program; | |
| LogClientServiceMapping(function_name, program_id, service_id); | |
| api()->glUseProgramFn(service_id); | |
| if (state_.current_program.get()) { | |
| program_manager()->UseProgram(state_.current_program.get()); | |
| } | |
| } | |
| void GLES2DecoderImpl::RenderWarning( | |
| const char* filename, int line, const std::string& msg) { | |
| logger_.LogMessage(filename, line, std::string("RENDER WARNING: ") + msg); | |
| } | |
| void GLES2DecoderImpl::PerformanceWarning( | |
| const char* filename, int line, const std::string& msg) { | |
| logger_.LogMessage(filename, line, | |
| std::string("PERFORMANCE WARNING: ") + msg); | |
| } | |
| void GLES2DecoderImpl::DoCopyBufferSubData(GLenum readtarget, | |
| GLenum writetarget, | |
| GLintptr readoffset, | |
| GLintptr writeoffset, | |
| GLsizeiptr size) { | |
| // Just delegate it. Some validation is actually done before this. | |
| buffer_manager()->ValidateAndDoCopyBufferSubData( | |
| &state_, error_state_.get(), readtarget, writetarget, readoffset, | |
| writeoffset, size); | |
| } | |
| bool GLES2DecoderImpl::PrepareTexturesForRender(bool* textures_set, | |
| const char* function_name) { | |
| DCHECK(state_.current_program.get()); | |
| *textures_set = false; | |
| const Program::SamplerIndices& sampler_indices = | |
| state_.current_program->sampler_indices(); | |
| for (size_t ii = 0; ii < sampler_indices.size(); ++ii) { | |
| const Program::UniformInfo* uniform_info = | |
| state_.current_program->GetUniformInfo(sampler_indices[ii]); | |
| DCHECK(uniform_info); | |
| for (size_t jj = 0; jj < uniform_info->texture_units.size(); ++jj) { | |
| GLuint texture_unit_index = uniform_info->texture_units[jj]; | |
| if (texture_unit_index < state_.texture_units.size()) { | |
| TextureUnit& texture_unit = state_.texture_units[texture_unit_index]; | |
| TextureRef* texture_ref = | |
| texture_unit.GetInfoForSamplerType(uniform_info->type); | |
| // Find if the texture is also a depth or stencil attachment | |
| // Regardless of whether depth/stencil writes and masks are enabled | |
| // If so, there's a drawing feedback loop. | |
| Framebuffer* framebuffer = | |
| GetFramebufferInfoForTarget(GL_DRAW_FRAMEBUFFER); | |
| if (framebuffer) { | |
| if (CheckDrawingFeedbackLoopsHelper( | |
| framebuffer->GetAttachment(GL_DEPTH_ATTACHMENT), texture_ref, | |
| function_name)) { | |
| return false; | |
| } | |
| if (CheckDrawingFeedbackLoopsHelper( | |
| framebuffer->GetAttachment(GL_STENCIL_ATTACHMENT), | |
| texture_ref, function_name)) { | |
| return false; | |
| } | |
| } | |
| GLenum textarget = GetBindTargetForSamplerType(uniform_info->type); | |
| const SamplerState& sampler_state = GetSamplerStateForTextureUnit( | |
| uniform_info->type, texture_unit_index); | |
| if (!texture_ref || | |
| !texture_manager()->CanRenderWithSampler( | |
| texture_ref, sampler_state)) { | |
| *textures_set = true; | |
| api()->glActiveTextureFn(GL_TEXTURE0 + texture_unit_index); | |
| api()->glBindTextureFn(textarget, texture_manager()->black_texture_id( | |
| uniform_info->type)); | |
| if (!texture_ref) { | |
| LOCAL_RENDER_WARNING( | |
| std::string("there is no texture bound to the unit ") + | |
| base::NumberToString(texture_unit_index)); | |
| } else { | |
| LOCAL_RENDER_WARNING( | |
| std::string("texture bound to texture unit ") + | |
| base::NumberToString(texture_unit_index) + | |
| " is not renderable. It might be non-power-of-2 or have" | |
| " incompatible texture filtering (maybe)?"); | |
| } | |
| continue; | |
| } else if (!texture_ref->texture()->CompatibleWithSamplerUniformType( | |
| uniform_info->type, sampler_state)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, function_name, | |
| (std::string("Texture bound to texture unit ") + | |
| base::NumberToString(texture_unit_index) + | |
| " with internal format " + | |
| GLES2Util::GetStringEnum( | |
| texture_ref->texture()->GetInternalFormatOfBaseLevel()) + | |
| " is not compatible with sampler type " + | |
| GLES2Util::GetStringEnum(uniform_info->type)) | |
| .c_str()); | |
| return false; | |
| } | |
| // Find if the texture is also a color attachment | |
| // If so, there's a drawing feedback loop. | |
| if (framebuffer) { | |
| for (GLsizei kk = 0; kk <= framebuffer->last_color_attachment_id(); | |
| ++kk) { | |
| GLenum attachment = static_cast<GLenum>(GL_COLOR_ATTACHMENT0 + kk); | |
| if (CheckDrawingFeedbackLoopsHelper( | |
| framebuffer->GetAttachment(attachment), texture_ref, | |
| function_name)) { | |
| return false; | |
| } | |
| } | |
| } | |
| } | |
| // else: should this be an error? | |
| } | |
| } | |
| return true; | |
| } | |
| void GLES2DecoderImpl::RestoreStateForTextures() { | |
| DCHECK(state_.current_program.get()); | |
| const Program::SamplerIndices& sampler_indices = | |
| state_.current_program->sampler_indices(); | |
| for (size_t ii = 0; ii < sampler_indices.size(); ++ii) { | |
| const Program::UniformInfo* uniform_info = | |
| state_.current_program->GetUniformInfo(sampler_indices[ii]); | |
| DCHECK(uniform_info); | |
| for (size_t jj = 0; jj < uniform_info->texture_units.size(); ++jj) { | |
| GLuint texture_unit_index = uniform_info->texture_units[jj]; | |
| if (texture_unit_index < state_.texture_units.size()) { | |
| TextureUnit& texture_unit = state_.texture_units[texture_unit_index]; | |
| TextureRef* texture_ref = | |
| texture_unit.GetInfoForSamplerType(uniform_info->type); | |
| const SamplerState& sampler_state = GetSamplerStateForTextureUnit( | |
| uniform_info->type, texture_unit_index); | |
| if (!texture_ref || | |
| !texture_manager()->CanRenderWithSampler( | |
| texture_ref, sampler_state)) { | |
| api()->glActiveTextureFn(GL_TEXTURE0 + texture_unit_index); | |
| // Get the texture_ref info that was previously bound here. | |
| texture_ref = | |
| texture_unit.GetInfoForTarget(texture_unit.bind_target); | |
| api()->glBindTextureFn(texture_unit.bind_target, | |
| texture_ref ? texture_ref->service_id() : 0); | |
| continue; | |
| } | |
| } | |
| } | |
| } | |
| // Set the active texture back to whatever the user had it as. | |
| api()->glActiveTextureFn(GL_TEXTURE0 + state_.active_texture_unit); | |
| } | |
| bool GLES2DecoderImpl::ClearUnclearedTextures() { | |
| // Only check if there are some uncleared textures. | |
| if (!texture_manager()->HaveUnsafeTextures()) { | |
| return true; | |
| } | |
| // 1: Check all textures we are about to render with. | |
| if (state_.current_program.get()) { | |
| const Program::SamplerIndices& sampler_indices = | |
| state_.current_program->sampler_indices(); | |
| for (size_t ii = 0; ii < sampler_indices.size(); ++ii) { | |
| const Program::UniformInfo* uniform_info = | |
| state_.current_program->GetUniformInfo(sampler_indices[ii]); | |
| DCHECK(uniform_info); | |
| for (size_t jj = 0; jj < uniform_info->texture_units.size(); ++jj) { | |
| GLuint texture_unit_index = uniform_info->texture_units[jj]; | |
| if (texture_unit_index < state_.texture_units.size()) { | |
| TextureUnit& texture_unit = state_.texture_units[texture_unit_index]; | |
| TextureRef* texture_ref = | |
| texture_unit.GetInfoForSamplerType(uniform_info->type); | |
| if (texture_ref && !texture_ref->texture()->SafeToRenderFrom()) { | |
| if (!texture_manager()->ClearRenderableLevels(this, texture_ref)) { | |
| return false; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| bool GLES2DecoderImpl::ValidateStencilStateForDraw(const char* function_name) { | |
| if (!state_.stencil_state_changed_since_validation) { | |
| return true; | |
| } | |
| GLenum stencil_format = GetBoundFramebufferStencilFormat(GL_DRAW_FRAMEBUFFER); | |
| uint8_t stencil_bits = GLES2Util::StencilBitsPerPixel(stencil_format); | |
| if (state_.enable_flags.stencil_test && stencil_bits > 0) { | |
| DCHECK_LE(stencil_bits, 8U); | |
| GLuint max_stencil_value = (1 << stencil_bits) - 1; | |
| GLint max_stencil_ref = static_cast<GLint>(max_stencil_value); | |
| bool different_refs = | |
| std::clamp(state_.stencil_front_ref, 0, max_stencil_ref) != | |
| std::clamp(state_.stencil_back_ref, 0, max_stencil_ref); | |
| bool different_writemasks = | |
| (state_.stencil_front_writemask & max_stencil_value) != | |
| (state_.stencil_back_writemask & max_stencil_value); | |
| bool different_value_masks = | |
| (state_.stencil_front_mask & max_stencil_value) != | |
| (state_.stencil_back_mask & max_stencil_value); | |
| if (different_refs || different_writemasks || different_value_masks) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "Front/back stencil settings do not match."); | |
| return false; | |
| } | |
| } | |
| state_.stencil_state_changed_since_validation = false; | |
| return true; | |
| } | |
| bool GLES2DecoderImpl::IsDrawValid(const char* function_name, | |
| GLuint max_vertex_accessed, | |
| bool instanced, | |
| GLsizei primcount, | |
| GLint basevertex, | |
| GLuint baseinstance) { | |
| DCHECK(instanced || primcount == 1); | |
| // NOTE: We specifically do not check current_program->IsValid() because | |
| // it could never be invalid since glUseProgram would have failed. While | |
| // glLinkProgram could later mark the program as invalid the previous | |
| // valid program will still function if it is still the current program. | |
| if (!state_.current_program.get()) { | |
| // The program does not exist. | |
| // But GL says no ERROR. | |
| LOCAL_RENDER_WARNING("Drawing with no current shader program."); | |
| return false; | |
| } | |
| // Perform extra stencil validation on ANGLE/D3D, and for all WebGL contexts. | |
| if (!feature_info_->feature_flags().separate_stencil_ref_mask_writemask) { | |
| if (!ValidateStencilStateForDraw(function_name)) { | |
| return false; | |
| } | |
| } | |
| if (!state_.vertex_attrib_manager->ValidateBindings( | |
| function_name, this, feature_info_.get(), buffer_manager(), | |
| state_.current_program.get(), max_vertex_accessed, instanced, | |
| primcount, basevertex, baseinstance)) { | |
| return false; | |
| } | |
| if (workarounds().disallow_large_instanced_draw) { | |
| const GLsizei kMaxInstancedDrawPrimitiveCount = 0x4000000; | |
| if (primcount > kMaxInstancedDrawPrimitiveCount) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_OUT_OF_MEMORY, function_name, | |
| "Instanced draw primcount too large for this platform"); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| void GLES2DecoderImpl::RestoreStateForAttrib( | |
| GLuint attrib_index, bool restore_array_binding) { | |
| const VertexAttrib* attrib = | |
| state_.vertex_attrib_manager->GetVertexAttrib(attrib_index); | |
| if (restore_array_binding) { | |
| const void* ptr = reinterpret_cast<const void*>(attrib->offset()); | |
| Buffer* buffer = attrib->buffer(); | |
| api()->glBindBufferFn(GL_ARRAY_BUFFER, buffer ? buffer->service_id() : 0); | |
| api()->glVertexAttribPointerFn(attrib_index, attrib->size(), attrib->type(), | |
| attrib->normalized(), attrib->gl_stride(), | |
| ptr); | |
| } | |
| // Attrib divisors should only be non-zero when the ANGLE_instanced_arrays | |
| // extension is available | |
| DCHECK(attrib->divisor() == 0 || | |
| feature_info_->feature_flags().angle_instanced_arrays); | |
| if (feature_info_->feature_flags().angle_instanced_arrays) | |
| api()->glVertexAttribDivisorANGLEFn(attrib_index, attrib->divisor()); | |
| api()->glBindBufferFn(GL_ARRAY_BUFFER, | |
| state_.bound_array_buffer.get() | |
| ? state_.bound_array_buffer->service_id() | |
| : 0); | |
| // Restore the vertex attrib array enable-state according to | |
| // the VertexAttrib enabled_in_driver value (which really represents the | |
| // state of the virtual context - not the driver - notably, above the | |
| // vertex array object emulation layer). | |
| if (attrib->enabled_in_driver()) { | |
| api()->glEnableVertexAttribArrayFn(attrib_index); | |
| } else { | |
| api()->glDisableVertexAttribArrayFn(attrib_index); | |
| } | |
| } | |
| bool GLES2DecoderImpl::AttribsTypeMatch() { | |
| if (!state_.current_program.get()) | |
| return true; | |
| const std::vector<uint32_t>& shader_attrib_active_mask = | |
| state_.current_program->vertex_input_active_mask(); | |
| const std::vector<uint32_t>& shader_attrib_type_mask = | |
| state_.current_program->vertex_input_base_type_mask(); | |
| const std::vector<uint32_t>& generic_vertex_attrib_type_mask = | |
| state_.generic_attrib_base_type_mask(); | |
| const std::vector<uint32_t>& vertex_attrib_array_enabled_mask = | |
| state_.vertex_attrib_manager->attrib_enabled_mask(); | |
| const std::vector<uint32_t>& vertex_attrib_array_type_mask = | |
| state_.vertex_attrib_manager->attrib_base_type_mask(); | |
| DCHECK_EQ(shader_attrib_active_mask.size(), | |
| shader_attrib_type_mask.size()); | |
| DCHECK_EQ(shader_attrib_active_mask.size(), | |
| generic_vertex_attrib_type_mask.size()); | |
| DCHECK_EQ(shader_attrib_active_mask.size(), | |
| vertex_attrib_array_enabled_mask.size()); | |
| DCHECK_EQ(shader_attrib_active_mask.size(), | |
| vertex_attrib_array_type_mask.size()); | |
| for (size_t ii = 0; ii < shader_attrib_active_mask.size(); ++ii) { | |
| uint32_t vertex_attrib_source_type_mask = | |
| (~vertex_attrib_array_enabled_mask[ii] & | |
| generic_vertex_attrib_type_mask[ii]) | | |
| (vertex_attrib_array_enabled_mask[ii] & | |
| vertex_attrib_array_type_mask[ii]); | |
| if ((shader_attrib_type_mask[ii] & shader_attrib_active_mask[ii]) != | |
| (vertex_attrib_source_type_mask & shader_attrib_active_mask[ii])) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| template <GLES2DecoderImpl::DrawArraysOption option> | |
| ALWAYS_INLINE bool GLES2DecoderImpl::CheckMultiDrawArraysVertices( | |
| const char* function_name, | |
| bool instanced, | |
| const GLint* firsts, | |
| const GLsizei* counts, | |
| const GLsizei* primcounts, | |
| const GLuint* baseinstances, | |
| GLsizei drawcount, | |
| GLuint* total_max_vertex_accessed, | |
| GLsizei* total_max_primcount) { | |
| if (option == DrawArraysOption::Default) { | |
| DCHECK_EQ(baseinstances, nullptr); | |
| } | |
| DCHECK_GE(drawcount, 0); | |
| for (GLsizei draw_id = 0; draw_id < drawcount; ++draw_id) { | |
| GLint first = UNSAFE_TODO(firsts[draw_id]); | |
| GLsizei count = UNSAFE_TODO(counts[draw_id]); | |
| GLsizei primcount = instanced ? UNSAFE_TODO(primcounts[draw_id]) : 1; | |
| GLuint baseinstance = (option == DrawArraysOption::UseBaseInstance) | |
| ? UNSAFE_TODO(baseinstances[draw_id]) | |
| : 0; | |
| // We have to check this here because the prototype for glDrawArrays | |
| // is GLint not GLsizei. | |
| if (first < 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "first < 0"); | |
| return false; | |
| } | |
| if (count < 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "count < 0"); | |
| return false; | |
| } | |
| if (primcount < 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "primcount < 0"); | |
| return false; | |
| } | |
| if (count == 0 || primcount == 0) { | |
| LOCAL_RENDER_WARNING("Render count or primcount is 0."); | |
| continue; | |
| } | |
| base::CheckedNumeric<GLuint> checked_max_vertex = first; | |
| checked_max_vertex += count - 1; | |
| // first and count-1 are both a non-negative int, so their sum fits an | |
| // unsigned int. | |
| GLuint max_vertex_accessed = 0; | |
| if (!checked_max_vertex.AssignIfValid(&max_vertex_accessed)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, | |
| "first + count overflow"); | |
| return false; | |
| } | |
| if (!IsDrawValid(function_name, max_vertex_accessed, instanced, primcount, | |
| 0, baseinstance)) { | |
| return false; | |
| } | |
| *total_max_vertex_accessed = | |
| std::max(*total_max_vertex_accessed, max_vertex_accessed); | |
| *total_max_primcount = std::max(*total_max_primcount, primcount); | |
| } | |
| return true; | |
| } | |
| ALWAYS_INLINE bool GLES2DecoderImpl::CheckTransformFeedback( | |
| const char* function_name, | |
| bool instanced, | |
| GLenum mode, | |
| const GLsizei* counts, | |
| const GLsizei* primcounts, | |
| GLsizei drawcount, | |
| GLsizei* vertices_drawn) { | |
| DCHECK(state_.bound_transform_feedback.get()); | |
| if (state_.bound_transform_feedback->active() && | |
| !state_.bound_transform_feedback->paused()) { | |
| if (mode != state_.bound_transform_feedback->primitive_mode()) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, function_name, | |
| "mode differs from active transformfeedback's primitiveMode"); | |
| return false; | |
| } | |
| for (GLsizei draw_id = 0; draw_id < drawcount; ++draw_id) { | |
| GLsizei count = UNSAFE_TODO(counts[draw_id]); | |
| GLsizei primcount = instanced ? UNSAFE_TODO(primcounts[draw_id]) : 1; | |
| bool valid = state_.bound_transform_feedback->GetVerticesNeededForDraw( | |
| mode, count, primcount, *vertices_drawn, vertices_drawn); | |
| if (!valid) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "integer overflow calculating number of vertices " | |
| "for transform feedback"); | |
| return false; | |
| } | |
| } | |
| if (!buffer_manager()->RequestBuffersAccess( | |
| error_state_.get(), state_.bound_transform_feedback.get(), | |
| state_.current_program->GetTransformFeedbackVaryingSizes(), | |
| *vertices_drawn, function_name, "transformfeedback buffers")) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| template <GLES2DecoderImpl::DrawArraysOption option> | |
| ALWAYS_INLINE error::Error GLES2DecoderImpl::DoMultiDrawArrays( | |
| const char* function_name, | |
| bool instanced, | |
| GLenum mode, | |
| const GLint* firsts, | |
| const GLsizei* counts, | |
| const GLsizei* primcounts, | |
| const GLuint* baseinstances, | |
| GLsizei drawcount) { | |
| if (option == DrawArraysOption::Default) { | |
| DCHECK_EQ(baseinstances, nullptr); | |
| } | |
| if (!validators_->draw_mode.IsValid(mode)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM(function_name, mode, "mode"); | |
| return error::kNoError; | |
| } | |
| if (drawcount < 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "drawcount < 0"); | |
| return error::kNoError; | |
| } | |
| if (!CheckBoundDrawFramebufferValid(function_name, true)) { | |
| return error::kNoError; | |
| } | |
| GLuint total_max_vertex_accessed = 0; | |
| GLsizei total_max_primcount = 0; | |
| if (!CheckMultiDrawArraysVertices<option>( | |
| function_name, instanced, firsts, counts, primcounts, baseinstances, | |
| drawcount, &total_max_vertex_accessed, &total_max_primcount)) { | |
| return error::kNoError; | |
| } | |
| if (total_max_primcount == 0) { | |
| return error::kNoError; | |
| } | |
| GLsizei transform_feedback_vertices = 0; | |
| if (feature_info_->IsWebGL2OrES3Context()) { | |
| if (!AttribsTypeMatch()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "vertexAttrib function must match shader attrib type"); | |
| return error::kNoError; | |
| } | |
| if (!CheckTransformFeedback(function_name, instanced, mode, counts, | |
| primcounts, drawcount, | |
| &transform_feedback_vertices)) { | |
| return error::kNoError; | |
| } | |
| if (!ValidateUniformBlockBackings(function_name)) { | |
| return error::kNoError; | |
| } | |
| } | |
| if (!ClearUnclearedTextures()) { | |
| // TODO(enga): Can this be GL_OUT_OF_MEMORY? | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "out of memory"); | |
| return error::kNoError; | |
| } | |
| // The branch with fixed attrib is not meant to be used | |
| // normally but just to pass OpenGL ES 2 conformance where there's no | |
| // basevertex and baseinstance support. | |
| { | |
| bool textures_set; | |
| if (!PrepareTexturesForRender(&textures_set, function_name)) { | |
| return error::kNoError; | |
| } | |
| ApplyDirtyState(); | |
| if (!ValidateAndAdjustDrawBuffers(function_name)) { | |
| return error::kNoError; | |
| } | |
| GLint draw_id_location = state_.current_program->draw_id_uniform_location(); | |
| GLint base_instance_location = | |
| state_.current_program->base_instance_uniform_location(); | |
| for (GLsizei draw_id = 0; draw_id < drawcount; ++draw_id) { | |
| GLint first = UNSAFE_TODO(firsts[draw_id]); | |
| GLsizei count = UNSAFE_TODO(counts[draw_id]); | |
| GLsizei primcount = instanced ? UNSAFE_TODO(primcounts[draw_id]) : 1; | |
| if (count == 0 || primcount == 0) { | |
| continue; | |
| } | |
| if (draw_id_location >= 0) { | |
| api()->glUniform1iFn(draw_id_location, draw_id); | |
| } | |
| if (!instanced) { | |
| api()->glDrawArraysFn(mode, first, count); | |
| } else { | |
| if (option != DrawArraysOption::UseBaseInstance) { | |
| api()->glDrawArraysInstancedANGLEFn(mode, first, count, primcount); | |
| } else { | |
| GLuint baseinstance = UNSAFE_TODO(baseinstances[draw_id]); | |
| if (base_instance_location >= 0) { | |
| api()->glUniform1iFn(base_instance_location, baseinstance); | |
| } | |
| api()->glDrawArraysInstancedBaseInstanceANGLEFn( | |
| mode, first, count, primcount, baseinstance); | |
| } | |
| } | |
| } | |
| if (state_.bound_transform_feedback.get()) { | |
| state_.bound_transform_feedback->OnVerticesDrawn( | |
| transform_feedback_vertices); | |
| } | |
| if (textures_set) { | |
| RestoreStateForTextures(); | |
| } | |
| // only reset base vertex and base instance shader variable when it's | |
| // possibly non-zero | |
| if (option == DrawArraysOption::UseBaseInstance) { | |
| if (base_instance_location >= 0) { | |
| api()->glUniform1iFn(base_instance_location, 0); | |
| } | |
| } | |
| } | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleDrawArrays(uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile cmds::DrawArrays& c = | |
| *static_cast<const volatile cmds::DrawArrays*>(cmd_data); | |
| GLint first = static_cast<GLint>(c.first); | |
| GLsizei count = static_cast<GLsizei>(c.count); | |
| return DoMultiDrawArrays<DrawArraysOption::Default>( | |
| "glDrawArrays", false, static_cast<GLenum>(c.mode), &first, &count, | |
| nullptr, nullptr, 1); | |
| } | |
| error::Error GLES2DecoderImpl::HandleDrawArraysInstancedANGLE( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::DrawArraysInstancedANGLE& c = | |
| *static_cast<const volatile gles2::cmds::DrawArraysInstancedANGLE*>( | |
| cmd_data); | |
| if (!features().angle_instanced_arrays) | |
| return error::kUnknownCommand; | |
| GLint first = static_cast<GLint>(c.first); | |
| GLsizei count = static_cast<GLsizei>(c.count); | |
| GLsizei primcount = static_cast<GLsizei>(c.primcount); | |
| return DoMultiDrawArrays<DrawArraysOption::Default>( | |
| "glDrawArraysInstancedANGLE", true, static_cast<GLenum>(c.mode), &first, | |
| &count, &primcount, nullptr, 1); | |
| } | |
| error::Error GLES2DecoderImpl::HandleDrawArraysInstancedBaseInstanceANGLE( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::DrawArraysInstancedBaseInstanceANGLE& c = | |
| *static_cast< | |
| const volatile gles2::cmds::DrawArraysInstancedBaseInstanceANGLE*>( | |
| cmd_data); | |
| if (!features().angle_instanced_arrays) | |
| return error::kUnknownCommand; | |
| if (!features().webgl_draw_instanced_base_vertex_base_instance && | |
| !features().webgl_multi_draw_instanced_base_vertex_base_instance) | |
| return error::kUnknownCommand; | |
| GLint first = static_cast<GLint>(c.first); | |
| GLsizei count = static_cast<GLsizei>(c.count); | |
| GLsizei primcount = static_cast<GLsizei>(c.primcount); | |
| GLuint baseInstances = static_cast<GLuint>(c.baseinstance); | |
| return DoMultiDrawArrays<DrawArraysOption::UseBaseInstance>( | |
| "glDrawArraysInstancedBaseInstanceANGLE", true, | |
| static_cast<GLenum>(c.mode), &first, &count, &primcount, &baseInstances, | |
| 1); | |
| } | |
| template <GLES2DecoderImpl::DrawElementsOption option> | |
| ALWAYS_INLINE bool GLES2DecoderImpl::CheckMultiDrawElementsVertices( | |
| const char* function_name, | |
| bool instanced, | |
| const GLsizei* counts, | |
| GLenum type, | |
| const int32_t* offsets, | |
| const GLsizei* primcounts, | |
| const GLint* basevertices, | |
| const GLuint* baseinstances, | |
| GLsizei drawcount, | |
| Buffer* element_array_buffer, | |
| GLuint* total_max_vertex_accessed, | |
| GLsizei* total_max_primcount) { | |
| if (option == DrawElementsOption::Default) { | |
| DCHECK_EQ(basevertices, nullptr); | |
| DCHECK_EQ(baseinstances, nullptr); | |
| } | |
| DCHECK_GE(drawcount, 0); | |
| for (GLsizei draw_id = 0; draw_id < drawcount; ++draw_id) { | |
| GLsizei count = UNSAFE_TODO(counts[draw_id]); | |
| GLsizei offset = UNSAFE_TODO(offsets[draw_id]); | |
| GLsizei primcount = instanced ? UNSAFE_TODO(primcounts[draw_id]) : 1; | |
| GLint basevertex = (option == DrawElementsOption::UseBaseVertexBaseInstance) | |
| ? UNSAFE_TODO(basevertices[draw_id]) | |
| : 0; | |
| GLint baseinstance = | |
| (option == DrawElementsOption::UseBaseVertexBaseInstance) | |
| ? UNSAFE_TODO(baseinstances[draw_id]) | |
| : 0; | |
| if (count < 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "count < 0"); | |
| return false; | |
| } | |
| if (offset < 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "offset < 0"); | |
| return false; | |
| } | |
| if (primcount < 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "primcount < 0"); | |
| return false; | |
| } | |
| if (count == 0 || primcount == 0) { | |
| continue; | |
| } | |
| GLuint max_vertex_accessed; | |
| if (!element_array_buffer->GetMaxValueForRange( | |
| offset, count, type, | |
| state_.enable_flags.primitive_restart_fixed_index, | |
| &max_vertex_accessed)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "range out of bounds for buffer"); | |
| return false; | |
| } | |
| if (!IsDrawValid(function_name, max_vertex_accessed, instanced, primcount, | |
| basevertex, baseinstance)) { | |
| return false; | |
| } | |
| *total_max_vertex_accessed = | |
| std::max(*total_max_vertex_accessed, max_vertex_accessed + basevertex); | |
| *total_max_primcount = std::max(*total_max_primcount, primcount); | |
| } | |
| return true; | |
| } | |
| template <GLES2DecoderImpl::DrawElementsOption option> | |
| ALWAYS_INLINE error::Error GLES2DecoderImpl::DoMultiDrawElements( | |
| const char* function_name, | |
| bool instanced, | |
| GLenum mode, | |
| const GLsizei* counts, | |
| GLenum type, | |
| const int32_t* offsets, | |
| const GLsizei* primcounts, | |
| const GLint* basevertices, | |
| const GLuint* baseinstances, | |
| GLsizei drawcount) { | |
| if (option == DrawElementsOption::Default) { | |
| DCHECK_EQ(basevertices, nullptr); | |
| DCHECK_EQ(baseinstances, nullptr); | |
| } | |
| if (!validators_->draw_mode.IsValid(mode)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM(function_name, mode, "mode"); | |
| return error::kNoError; | |
| } | |
| if (!validators_->index_type.IsValid(type)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM(function_name, type, "type"); | |
| return error::kNoError; | |
| } | |
| if (drawcount < 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "drawcount < 0"); | |
| return error::kNoError; | |
| } | |
| if (!CheckBoundDrawFramebufferValid(function_name, true)) { | |
| return error::kNoError; | |
| } | |
| Buffer* element_array_buffer = buffer_manager()->RequestBufferAccess( | |
| &state_, error_state_.get(), GL_ELEMENT_ARRAY_BUFFER, function_name); | |
| if (!element_array_buffer) { | |
| return error::kNoError; | |
| } | |
| if (state_.bound_transform_feedback.get() && | |
| state_.bound_transform_feedback->active() && | |
| !state_.bound_transform_feedback->paused()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "transformfeedback is active and not paused"); | |
| return error::kNoError; | |
| } | |
| GLuint total_max_vertex_accessed = 0; | |
| GLsizei total_max_primcount = 0; | |
| if (!CheckMultiDrawElementsVertices<option>( | |
| function_name, instanced, counts, type, offsets, primcounts, | |
| basevertices, baseinstances, drawcount, element_array_buffer, | |
| &total_max_vertex_accessed, &total_max_primcount)) { | |
| return error::kNoError; | |
| } | |
| if (total_max_primcount == 0) { | |
| return error::kNoError; | |
| } | |
| if (feature_info_->IsWebGL2OrES3Context()) { | |
| if (!AttribsTypeMatch()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "vertexAttrib function must match shader attrib type"); | |
| return error::kNoError; | |
| } | |
| if (!ValidateUniformBlockBackings(function_name)) { | |
| return error::kNoError; | |
| } | |
| } | |
| if (!ClearUnclearedTextures()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "out of memory"); | |
| return error::kNoError; | |
| } | |
| // The branch with fixed attrib is not meant to be used | |
| // normally But just to pass OpenGL ES 2 conformance where there's no | |
| // basevertex and baseinstance support. | |
| { | |
| bool textures_set; | |
| if (!PrepareTexturesForRender(&textures_set, function_name)) { | |
| return error::kNoError; | |
| } | |
| ApplyDirtyState(); | |
| // TODO(gman): Refactor to hide these details in BufferManager or | |
| // VertexAttribManager. | |
| bool used_client_side_array = false; | |
| if (element_array_buffer->IsClientSideArray()) { | |
| used_client_side_array = true; | |
| api()->glBindBufferFn(GL_ELEMENT_ARRAY_BUFFER, 0); | |
| } | |
| if (!ValidateAndAdjustDrawBuffers(function_name)) { | |
| return error::kNoError; | |
| } | |
| if (state_.enable_flags.primitive_restart_fixed_index && | |
| feature_info_->feature_flags().emulate_primitive_restart_fixed_index) { | |
| api()->glEnableFn(GL_PRIMITIVE_RESTART); | |
| buffer_manager()->SetPrimitiveRestartFixedIndexIfNecessary(type); | |
| } | |
| GLint draw_id_location = state_.current_program->draw_id_uniform_location(); | |
| GLint base_vertex_location = | |
| state_.current_program->base_vertex_uniform_location(); | |
| GLint base_instance_location = | |
| state_.current_program->base_instance_uniform_location(); | |
| for (GLsizei draw_id = 0; draw_id < drawcount; ++draw_id) { | |
| GLsizei count = UNSAFE_TODO(counts[draw_id]); | |
| GLsizei offset = UNSAFE_TODO(offsets[draw_id]); | |
| GLsizei primcount = instanced ? UNSAFE_TODO(primcounts[draw_id]) : 1; | |
| if (count == 0 || primcount == 0) { | |
| continue; | |
| } | |
| const GLvoid* indices = reinterpret_cast<const GLvoid*>(offset); | |
| if (used_client_side_array) { | |
| indices = element_array_buffer->GetRange(offset, 0); | |
| } | |
| if (draw_id_location >= 0) { | |
| api()->glUniform1iFn(draw_id_location, draw_id); | |
| } | |
| if (!instanced) { | |
| api()->glDrawElementsFn(mode, count, type, indices); | |
| } else { | |
| if (option == DrawElementsOption::Default) { | |
| api()->glDrawElementsInstancedANGLEFn(mode, count, type, indices, | |
| primcount); | |
| } else { | |
| GLint basevertex = UNSAFE_TODO(basevertices[draw_id]); | |
| GLuint baseinstance = UNSAFE_TODO(baseinstances[draw_id]); | |
| if (base_vertex_location >= 0) { | |
| api()->glUniform1iFn(base_vertex_location, basevertex); | |
| } | |
| if (base_instance_location >= 0) { | |
| api()->glUniform1iFn(base_instance_location, baseinstance); | |
| } | |
| api()->glDrawElementsInstancedBaseVertexBaseInstanceANGLEFn( | |
| mode, count, type, indices, primcount, basevertex, baseinstance); | |
| } | |
| } | |
| } | |
| if (state_.enable_flags.primitive_restart_fixed_index && | |
| feature_info_->feature_flags().emulate_primitive_restart_fixed_index) { | |
| api()->glDisableFn(GL_PRIMITIVE_RESTART); | |
| } | |
| if (used_client_side_array) { | |
| api()->glBindBufferFn(GL_ELEMENT_ARRAY_BUFFER, | |
| element_array_buffer->service_id()); | |
| } | |
| if (textures_set) { | |
| RestoreStateForTextures(); | |
| } | |
| // only reset base vertex and base instance shader variable when it's | |
| // possibly non-zero | |
| if (option == DrawElementsOption::UseBaseVertexBaseInstance) { | |
| if (base_vertex_location >= 0) { | |
| api()->glUniform1iFn(base_vertex_location, 0); | |
| } | |
| if (base_instance_location >= 0) { | |
| api()->glUniform1iFn(base_instance_location, 0); | |
| } | |
| } | |
| } | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleDrawElements( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::DrawElements& c = | |
| *static_cast<const volatile gles2::cmds::DrawElements*>(cmd_data); | |
| GLsizei count = static_cast<GLsizei>(c.count); | |
| int32_t offset = static_cast<int32_t>(c.index_offset); | |
| return DoMultiDrawElements<DrawElementsOption::Default>( | |
| "glDrawElements", false, static_cast<GLenum>(c.mode), &count, | |
| static_cast<GLenum>(c.type), &offset, nullptr, nullptr, nullptr, 1); | |
| } | |
| error::Error GLES2DecoderImpl::HandleDrawElementsInstancedANGLE( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::DrawElementsInstancedANGLE& c = | |
| *static_cast<const volatile gles2::cmds::DrawElementsInstancedANGLE*>( | |
| cmd_data); | |
| if (!features().angle_instanced_arrays) | |
| return error::kUnknownCommand; | |
| GLsizei count = static_cast<GLsizei>(c.count); | |
| int32_t offset = static_cast<int32_t>(c.index_offset); | |
| GLsizei primcount = static_cast<GLsizei>(c.primcount); | |
| return DoMultiDrawElements<DrawElementsOption::Default>( | |
| "glDrawElementsInstancedANGLE", true, static_cast<GLenum>(c.mode), &count, | |
| static_cast<GLenum>(c.type), &offset, &primcount, nullptr, nullptr, 1); | |
| } | |
| error::Error | |
| GLES2DecoderImpl::HandleDrawElementsInstancedBaseVertexBaseInstanceANGLE( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::DrawElementsInstancedBaseVertexBaseInstanceANGLE& | |
| c = *static_cast<const volatile gles2::cmds:: | |
| DrawElementsInstancedBaseVertexBaseInstanceANGLE*>( | |
| cmd_data); | |
| if (!features().angle_instanced_arrays) | |
| return error::kUnknownCommand; | |
| GLsizei count = static_cast<GLsizei>(c.count); | |
| int32_t offset = static_cast<int32_t>(c.index_offset); | |
| GLsizei primcount = static_cast<GLsizei>(c.primcount); | |
| GLint basevertex = static_cast<GLsizei>(c.basevertex); | |
| GLuint baseinstance = static_cast<GLsizei>(c.baseinstance); | |
| return DoMultiDrawElements<DrawElementsOption::UseBaseVertexBaseInstance>( | |
| "glDrawElementsInstancedBaseVertexBaseInstanceANGLE", true, | |
| static_cast<GLenum>(c.mode), &count, static_cast<GLenum>(c.type), &offset, | |
| &primcount, &basevertex, &baseinstance, 1); | |
| } | |
| void GLES2DecoderImpl::DoMultiDrawBeginCHROMIUM(GLsizei drawcount) { | |
| if (!multi_draw_manager_->Begin(drawcount)) { | |
| MarkContextLost(error::kGuilty); | |
| group_->LoseContexts(error::kInnocent); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoMultiDrawEndCHROMIUM() { | |
| MultiDrawManager::ResultData result; | |
| if (!multi_draw_manager_->End(&result)) { | |
| MarkContextLost(error::kGuilty); | |
| group_->LoseContexts(error::kInnocent); | |
| return; | |
| } | |
| switch (result.draw_function) { | |
| case MultiDrawManager::DrawFunction::DrawArrays: | |
| DoMultiDrawArrays<DrawArraysOption::Default>( | |
| "glMultiDrawArraysWEBGL", false, result.mode, result.firsts.data(), | |
| result.counts.data(), nullptr, nullptr, result.drawcount); | |
| break; | |
| case MultiDrawManager::DrawFunction::DrawArraysInstanced: | |
| DoMultiDrawArrays<DrawArraysOption::Default>( | |
| "glMultiDrawArraysInstancedWEBGL", true, result.mode, | |
| result.firsts.data(), result.counts.data(), | |
| result.instance_counts.data(), nullptr, result.drawcount); | |
| break; | |
| case MultiDrawManager::DrawFunction::DrawArraysInstancedBaseInstance: | |
| DoMultiDrawArrays<DrawArraysOption::UseBaseInstance>( | |
| "glMultiDrawArraysInstancedBaseInstanceWEBGL", true, result.mode, | |
| result.firsts.data(), result.counts.data(), | |
| result.instance_counts.data(), result.baseinstances.data(), | |
| result.drawcount); | |
| break; | |
| case MultiDrawManager::DrawFunction::DrawElements: | |
| DoMultiDrawElements<DrawElementsOption::Default>( | |
| "glMultiDrawElementsWEBGL", false, result.mode, result.counts.data(), | |
| result.type, result.offsets.data(), nullptr, nullptr, nullptr, | |
| result.drawcount); | |
| break; | |
| case MultiDrawManager::DrawFunction::DrawElementsInstanced: | |
| DoMultiDrawElements<DrawElementsOption::Default>( | |
| "glMultiDrawElementsInstancedWEBGL", true, result.mode, | |
| result.counts.data(), result.type, result.offsets.data(), | |
| result.instance_counts.data(), nullptr, nullptr, result.drawcount); | |
| break; | |
| case MultiDrawManager::DrawFunction:: | |
| DrawElementsInstancedBaseVertexBaseInstance: | |
| DoMultiDrawElements<DrawElementsOption::UseBaseVertexBaseInstance>( | |
| "glMultiDrawElementsInstancedBaseVertexBaseInstanceWEBGL", true, | |
| result.mode, result.counts.data(), result.type, result.offsets.data(), | |
| result.instance_counts.data(), result.basevertices.data(), | |
| result.baseinstances.data(), result.drawcount); | |
| break; | |
| default: | |
| NOTREACHED(); | |
| } | |
| } | |
| error::Error GLES2DecoderImpl::HandleMultiDrawArraysCHROMIUM( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::MultiDrawArraysCHROMIUM& c = | |
| *static_cast<const volatile gles2::cmds::MultiDrawArraysCHROMIUM*>( | |
| cmd_data); | |
| if (!features().webgl_multi_draw) { | |
| return error::kUnknownCommand; | |
| } | |
| GLenum mode = static_cast<GLenum>(c.mode); | |
| GLsizei drawcount = static_cast<GLsizei>(c.drawcount); | |
| auto firsts = GetSharedMemoryAsSpan<const GLint>( | |
| c.firsts_shm_id, c.firsts_shm_offset, drawcount); | |
| auto counts = GetSharedMemoryAsSpan<const GLsizei>( | |
| c.counts_shm_id, c.counts_shm_offset, drawcount); | |
| if (!firsts.has_value() || !counts.has_value()) { | |
| return error::kOutOfBounds; | |
| } | |
| if (!multi_draw_manager_->MultiDrawArrays(mode, *firsts, *counts)) { | |
| return error::kInvalidArguments; | |
| } | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleMultiDrawArraysInstancedCHROMIUM( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::MultiDrawArraysInstancedCHROMIUM& c = | |
| *static_cast< | |
| const volatile gles2::cmds::MultiDrawArraysInstancedCHROMIUM*>( | |
| cmd_data); | |
| if (!features().webgl_multi_draw) { | |
| return error::kUnknownCommand; | |
| } | |
| GLenum mode = static_cast<GLenum>(c.mode); | |
| GLsizei drawcount = static_cast<GLsizei>(c.drawcount); | |
| auto firsts = GetSharedMemoryAsSpan<const GLint>( | |
| c.firsts_shm_id, c.firsts_shm_offset, drawcount); | |
| auto counts = GetSharedMemoryAsSpan<const GLsizei>( | |
| c.counts_shm_id, c.counts_shm_offset, drawcount); | |
| auto instance_counts = GetSharedMemoryAsSpan<const GLsizei>( | |
| c.instance_counts_shm_id, c.instance_counts_shm_offset, drawcount); | |
| if (!firsts.has_value() || !counts.has_value() || | |
| !instance_counts.has_value()) { | |
| return error::kOutOfBounds; | |
| } | |
| if (!multi_draw_manager_->MultiDrawArraysInstanced(mode, *firsts, *counts, | |
| *instance_counts)) { | |
| return error::kInvalidArguments; | |
| } | |
| return error::kNoError; | |
| } | |
| error::Error | |
| GLES2DecoderImpl::HandleMultiDrawArraysInstancedBaseInstanceCHROMIUM( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::MultiDrawArraysInstancedBaseInstanceCHROMIUM& c = | |
| *static_cast<const volatile gles2::cmds:: | |
| MultiDrawArraysInstancedBaseInstanceCHROMIUM*>(cmd_data); | |
| if (!features().webgl_multi_draw_instanced_base_vertex_base_instance) { | |
| return error::kUnknownCommand; | |
| } | |
| GLenum mode = static_cast<GLenum>(c.mode); | |
| GLsizei drawcount = static_cast<GLsizei>(c.drawcount); | |
| auto firsts = GetSharedMemoryAsSpan<const GLint>( | |
| c.firsts_shm_id, c.firsts_shm_offset, drawcount); | |
| auto counts = GetSharedMemoryAsSpan<const GLsizei>( | |
| c.counts_shm_id, c.counts_shm_offset, drawcount); | |
| auto instance_counts = GetSharedMemoryAsSpan<const GLsizei>( | |
| c.instance_counts_shm_id, c.instance_counts_shm_offset, drawcount); | |
| auto baseinstances = GetSharedMemoryAsSpan<const GLuint>( | |
| c.baseinstances_shm_id, c.baseinstances_shm_offset, drawcount); | |
| if (!firsts.has_value() || !counts.has_value() || | |
| !instance_counts.has_value() || !baseinstances.has_value()) { | |
| return error::kOutOfBounds; | |
| } | |
| if (!multi_draw_manager_->MultiDrawArraysInstancedBaseInstance( | |
| mode, *firsts, *counts, *instance_counts, *baseinstances)) { | |
| return error::kInvalidArguments; | |
| } | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleMultiDrawElementsCHROMIUM( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::MultiDrawElementsCHROMIUM& c = | |
| *static_cast<const volatile gles2::cmds::MultiDrawElementsCHROMIUM*>( | |
| cmd_data); | |
| if (!features().webgl_multi_draw) { | |
| return error::kUnknownCommand; | |
| } | |
| GLenum mode = static_cast<GLenum>(c.mode); | |
| GLenum type = static_cast<GLenum>(c.type); | |
| GLsizei drawcount = static_cast<GLsizei>(c.drawcount); | |
| auto counts = GetSharedMemoryAsSpan<const GLsizei>( | |
| c.counts_shm_id, c.counts_shm_offset, drawcount); | |
| auto offsets = GetSharedMemoryAsSpan<const GLsizei>( | |
| c.offsets_shm_id, c.offsets_shm_offset, drawcount); | |
| if (!counts.has_value() || !offsets.has_value()) { | |
| return error::kOutOfBounds; | |
| } | |
| if (!multi_draw_manager_->MultiDrawElements(mode, *counts, type, *offsets)) { | |
| return error::kInvalidArguments; | |
| } | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleMultiDrawElementsInstancedCHROMIUM( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::MultiDrawElementsInstancedCHROMIUM& c = | |
| *static_cast< | |
| const volatile gles2::cmds::MultiDrawElementsInstancedCHROMIUM*>( | |
| cmd_data); | |
| if (!features().webgl_multi_draw) { | |
| return error::kUnknownCommand; | |
| } | |
| GLenum mode = static_cast<GLenum>(c.mode); | |
| GLenum type = static_cast<GLenum>(c.type); | |
| GLsizei drawcount = static_cast<GLsizei>(c.drawcount); | |
| auto counts = GetSharedMemoryAsSpan<const GLsizei>( | |
| c.counts_shm_id, c.counts_shm_offset, drawcount); | |
| auto offsets = GetSharedMemoryAsSpan<const GLsizei>( | |
| c.offsets_shm_id, c.offsets_shm_offset, drawcount); | |
| auto instance_counts = GetSharedMemoryAsSpan<const GLsizei>( | |
| c.instance_counts_shm_id, c.instance_counts_shm_offset, drawcount); | |
| if (!counts.has_value() || !offsets.has_value() || | |
| !instance_counts.has_value()) { | |
| return error::kOutOfBounds; | |
| } | |
| if (!multi_draw_manager_->MultiDrawElementsInstanced( | |
| mode, *counts, type, *offsets, *instance_counts)) { | |
| return error::kInvalidArguments; | |
| } | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl:: | |
| HandleMultiDrawElementsInstancedBaseVertexBaseInstanceCHROMIUM( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds:: | |
| MultiDrawElementsInstancedBaseVertexBaseInstanceCHROMIUM& c = | |
| *static_cast< | |
| const volatile gles2::cmds:: | |
| MultiDrawElementsInstancedBaseVertexBaseInstanceCHROMIUM*>( | |
| cmd_data); | |
| if (!features().webgl_multi_draw_instanced_base_vertex_base_instance) { | |
| return error::kUnknownCommand; | |
| } | |
| GLenum mode = static_cast<GLenum>(c.mode); | |
| GLenum type = static_cast<GLenum>(c.type); | |
| GLsizei drawcount = static_cast<GLsizei>(c.drawcount); | |
| auto counts = GetSharedMemoryAsSpan<const GLsizei>( | |
| c.counts_shm_id, c.counts_shm_offset, drawcount); | |
| auto offsets = GetSharedMemoryAsSpan<const GLsizei>( | |
| c.offsets_shm_id, c.offsets_shm_offset, drawcount); | |
| auto instance_counts = GetSharedMemoryAsSpan<const GLsizei>( | |
| c.instance_counts_shm_id, c.instance_counts_shm_offset, drawcount); | |
| auto basevertices = GetSharedMemoryAsSpan<const GLint>( | |
| c.basevertices_shm_id, c.basevertices_shm_offset, drawcount); | |
| auto baseinstances = GetSharedMemoryAsSpan<const GLuint>( | |
| c.baseinstances_shm_id, c.baseinstances_shm_offset, drawcount); | |
| if (!counts.has_value() || !offsets.has_value() || | |
| !instance_counts.has_value() || !basevertices.has_value() || | |
| !baseinstances.has_value()) { | |
| return error::kOutOfBounds; | |
| } | |
| if (!multi_draw_manager_->MultiDrawElementsInstancedBaseVertexBaseInstance( | |
| mode, *counts, type, *offsets, *instance_counts, *basevertices, | |
| *baseinstances)) { | |
| return error::kInvalidArguments; | |
| } | |
| return error::kNoError; | |
| } | |
| GLuint GLES2DecoderImpl::DoGetMaxValueInBufferCHROMIUM( | |
| GLuint buffer_id, GLsizei count, GLenum type, GLuint offset) { | |
| GLuint max_vertex_accessed = 0; | |
| Buffer* buffer = GetBuffer(buffer_id); | |
| if (!buffer) { | |
| // TODO(gman): Should this be a GL error or a command buffer error? | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "GetMaxValueInBufferCHROMIUM", "unknown buffer"); | |
| } else { | |
| // The max value is used here to emulate client-side vertex | |
| // arrays, by uploading enough vertices into buffer objects to | |
| // cover the DrawElements call. Baking the primitive restart bit | |
| // into this result isn't strictly correct in all cases; the | |
| // client side code should pass down the bit and decide how to use | |
| // the result. However, the only caller makes the draw call | |
| // immediately afterward, so the state won't change between this | |
| // query and the draw call. | |
| if (!buffer->GetMaxValueForRange( | |
| offset, count, type, | |
| state_.enable_flags.primitive_restart_fixed_index, | |
| &max_vertex_accessed)) { | |
| // TODO(gman): Should this be a GL error or a command buffer error? | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, | |
| "GetMaxValueInBufferCHROMIUM", "range out of bounds for buffer"); | |
| } | |
| } | |
| return max_vertex_accessed; | |
| } | |
| void GLES2DecoderImpl::DoShaderSource( | |
| GLuint client_id, GLsizei count, const char** data, const GLint* length) { | |
| std::string str; | |
| for (GLsizei ii = 0; ii < count; ++ii) { | |
| if (length && UNSAFE_TODO(length[ii]) > 0) { | |
| str.append(UNSAFE_TODO(data[ii]), UNSAFE_TODO(length[ii])); | |
| } else { | |
| str.append(UNSAFE_TODO(data[ii])); | |
| } | |
| } | |
| size_t len = str.size(); | |
| // Accommodate gles2_conform_tests | |
| // where there are '\0' at the end of the shaders | |
| while (len > 0 && str[len - 1] == '\0') { | |
| len -= 1; | |
| } | |
| // Delegate validation of the incoming shader source to ANGLE's | |
| // shader translator. | |
| Shader* shader = GetShaderInfoNotProgram(client_id, "glShaderSource"); | |
| if (!shader) { | |
| return; | |
| } | |
| // Note: We don't actually call glShaderSource here. We wait until | |
| // we actually compile the shader. | |
| shader->set_source(str); | |
| } | |
| void GLES2DecoderImpl::DoTransformFeedbackVaryings( | |
| GLuint client_program_id, GLsizei count, const char* const* varyings, | |
| GLenum buffer_mode) { | |
| Program* program = GetProgramInfoNotShader( | |
| client_program_id, "glTransformFeedbackVaryings"); | |
| if (!program) { | |
| return; | |
| } | |
| program->TransformFeedbackVaryings(count, varyings, buffer_mode); | |
| } | |
| scoped_refptr<ShaderTranslatorInterface> GLES2DecoderImpl::GetTranslator( | |
| GLenum type) { | |
| return type == GL_VERTEX_SHADER ? vertex_translator_ : fragment_translator_; | |
| } | |
| scoped_refptr<ShaderTranslatorInterface> | |
| GLES2DecoderImpl::GetOrCreateTranslator(GLenum type) { | |
| if (!InitializeShaderTranslator()) { | |
| return nullptr; | |
| } | |
| return GetTranslator(type); | |
| } | |
| void GLES2DecoderImpl::DoCompileShader(GLuint client_id) { | |
| TRACE_EVENT0("gpu", "GLES2DecoderImpl::DoCompileShader"); | |
| Shader* shader = GetShaderInfoNotProgram(client_id, "glCompileShader"); | |
| if (!shader) { | |
| return; | |
| } | |
| scoped_refptr<ShaderTranslatorInterface> translator; | |
| if (!feature_info_->disable_shader_translator()) | |
| translator = GetOrCreateTranslator(shader->shader_type()); | |
| const Shader::TranslatedShaderSourceType source_type = | |
| feature_info_->feature_flags().angle_translated_shader_source ? | |
| Shader::kANGLE : Shader::kGL; | |
| shader->RequestCompile(translator, source_type); | |
| } | |
| void GLES2DecoderImpl::DoGetShaderiv(GLuint shader_id, | |
| GLenum pname, | |
| GLint* params, | |
| GLsizei params_size) { | |
| Shader* shader = GetShaderInfoNotProgram(shader_id, "glGetShaderiv"); | |
| if (!shader) { | |
| return; | |
| } | |
| if (pname == GL_COMPILE_STATUS) { | |
| if (shader->HasCompiled()) { | |
| *params = compile_shader_always_succeeds_ ? true : shader->valid(); | |
| return; | |
| } | |
| // Lookup if there is compiled shader cache | |
| if (program_manager()->HasCachedCompileStatus(shader)) { | |
| // Only successful compile is cached | |
| // Fail-to-compile shader is not cached as needs compiling | |
| // to get log info | |
| *params = true; | |
| return; | |
| } | |
| } | |
| // Compile now for statuses that require it. | |
| switch (pname) { | |
| case GL_COMPILE_STATUS: | |
| case GL_INFO_LOG_LENGTH: | |
| case GL_TRANSLATED_SHADER_SOURCE_LENGTH_ANGLE: | |
| CompileShaderAndExitCommandProcessingEarly(shader); | |
| break; | |
| default: | |
| break; | |
| } | |
| switch (pname) { | |
| case GL_SHADER_SOURCE_LENGTH: | |
| *params = shader->source().size(); | |
| if (*params) | |
| ++(*params); | |
| return; | |
| case GL_COMPILE_STATUS: | |
| *params = compile_shader_always_succeeds_ ? true : shader->valid(); | |
| return; | |
| case GL_INFO_LOG_LENGTH: | |
| *params = shader->log_info().size(); | |
| if (*params) | |
| ++(*params); | |
| return; | |
| case GL_TRANSLATED_SHADER_SOURCE_LENGTH_ANGLE: | |
| *params = shader->translated_source().size(); | |
| if (*params) | |
| ++(*params); | |
| return; | |
| default: | |
| break; | |
| } | |
| api()->glGetShaderivFn(shader->service_id(), pname, params); | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetShaderSource( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::GetShaderSource& c = | |
| *static_cast<const volatile gles2::cmds::GetShaderSource*>(cmd_data); | |
| GLuint shader_id = c.shader; | |
| uint32_t bucket_id = static_cast<uint32_t>(c.bucket_id); | |
| Bucket* bucket = CreateBucket(bucket_id); | |
| Shader* shader = GetShaderInfoNotProgram(shader_id, "glGetShaderSource"); | |
| if (!shader || shader->source().empty()) { | |
| bucket->SetSize(0); | |
| return error::kNoError; | |
| } | |
| bucket->SetFromString(shader->source().c_str()); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetTranslatedShaderSourceANGLE( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::GetTranslatedShaderSourceANGLE& c = | |
| *static_cast<const volatile gles2::cmds::GetTranslatedShaderSourceANGLE*>( | |
| cmd_data); | |
| GLuint shader_id = c.shader; | |
| uint32_t bucket_id = static_cast<uint32_t>(c.bucket_id); | |
| Bucket* bucket = CreateBucket(bucket_id); | |
| Shader* shader = GetShaderInfoNotProgram( | |
| shader_id, "glGetTranslatedShaderSourceANGLE"); | |
| if (!shader) { | |
| bucket->SetSize(0); | |
| return error::kNoError; | |
| } | |
| // Make sure translator has been utilized in compile. | |
| CompileShaderAndExitCommandProcessingEarly(shader); | |
| bucket->SetFromString(shader->translated_source().c_str()); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetProgramInfoLog( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::GetProgramInfoLog& c = | |
| *static_cast<const volatile gles2::cmds::GetProgramInfoLog*>(cmd_data); | |
| GLuint program_id = c.program; | |
| uint32_t bucket_id = static_cast<uint32_t>(c.bucket_id); | |
| Bucket* bucket = CreateBucket(bucket_id); | |
| Program* program = GetProgramInfoNotShader( | |
| program_id, "glGetProgramInfoLog"); | |
| if (!program || !program->log_info()) { | |
| bucket->SetFromString(""); | |
| return error::kNoError; | |
| } | |
| bucket->SetFromString(program->log_info()->c_str()); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetShaderInfoLog( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::GetShaderInfoLog& c = | |
| *static_cast<const volatile gles2::cmds::GetShaderInfoLog*>(cmd_data); | |
| GLuint shader_id = c.shader; | |
| uint32_t bucket_id = static_cast<uint32_t>(c.bucket_id); | |
| Bucket* bucket = CreateBucket(bucket_id); | |
| Shader* shader = GetShaderInfoNotProgram(shader_id, "glGetShaderInfoLog"); | |
| if (!shader) { | |
| bucket->SetFromString(""); | |
| return error::kNoError; | |
| } | |
| // Shader must be compiled in order to get the info log. | |
| CompileShaderAndExitCommandProcessingEarly(shader); | |
| bucket->SetFromString(shader->log_info().c_str()); | |
| return error::kNoError; | |
| } | |
| bool GLES2DecoderImpl::DoIsEnabled(GLenum cap) { | |
| return state_.GetEnabled(cap); | |
| } | |
| bool GLES2DecoderImpl::DoIsEnablediOES(GLenum target, GLuint index) { | |
| // Note (crbug.com/1058744): not implemented for validating command decoder | |
| return false; | |
| } | |
| bool GLES2DecoderImpl::DoIsBuffer(GLuint client_id) { | |
| const Buffer* buffer = GetBuffer(client_id); | |
| return buffer && buffer->IsValid() && !buffer->IsDeleted(); | |
| } | |
| bool GLES2DecoderImpl::DoIsFramebuffer(GLuint client_id) { | |
| const Framebuffer* framebuffer = | |
| GetFramebuffer(client_id); | |
| return framebuffer && framebuffer->IsValid() && !framebuffer->IsDeleted(); | |
| } | |
| bool GLES2DecoderImpl::DoIsProgram(GLuint client_id) { | |
| // IsProgram is true for programs as soon as they are created, until they are | |
| // deleted and no longer in use. | |
| const Program* program = GetProgram(client_id); | |
| return program != nullptr && !program->IsDeleted(); | |
| } | |
| bool GLES2DecoderImpl::DoIsRenderbuffer(GLuint client_id) { | |
| const Renderbuffer* renderbuffer = | |
| GetRenderbuffer(client_id); | |
| return renderbuffer && renderbuffer->IsValid() && !renderbuffer->IsDeleted(); | |
| } | |
| bool GLES2DecoderImpl::DoIsShader(GLuint client_id) { | |
| // IsShader is true for shaders as soon as they are created, until they | |
| // are deleted and not attached to any programs. | |
| const Shader* shader = GetShader(client_id); | |
| return shader != nullptr && !shader->IsDeleted(); | |
| } | |
| bool GLES2DecoderImpl::DoIsTexture(GLuint client_id) { | |
| const TextureRef* texture_ref = GetTexture(client_id); | |
| return texture_ref && texture_ref->texture()->IsValid(); | |
| } | |
| bool GLES2DecoderImpl::DoIsSampler(GLuint client_id) { | |
| const Sampler* sampler = GetSampler(client_id); | |
| return sampler && !sampler->IsDeleted(); | |
| } | |
| bool GLES2DecoderImpl::DoIsTransformFeedback(GLuint client_id) { | |
| const TransformFeedback* transform_feedback = | |
| GetTransformFeedback(client_id); | |
| return transform_feedback && transform_feedback->has_been_bound(); | |
| } | |
| void GLES2DecoderImpl::DoAttachShader( | |
| GLuint program_client_id, GLint shader_client_id) { | |
| Program* program = GetProgramInfoNotShader( | |
| program_client_id, "glAttachShader"); | |
| if (!program) { | |
| return; | |
| } | |
| Shader* shader = GetShaderInfoNotProgram(shader_client_id, "glAttachShader"); | |
| if (!shader) { | |
| return; | |
| } | |
| if (!program->AttachShader(shader_manager(), shader)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, | |
| "glAttachShader", | |
| "can not attach more than one shader of the same type."); | |
| return; | |
| } | |
| api()->glAttachShaderFn(program->service_id(), shader->service_id()); | |
| } | |
| void GLES2DecoderImpl::DoDetachShader( | |
| GLuint program_client_id, GLint shader_client_id) { | |
| Program* program = GetProgramInfoNotShader( | |
| program_client_id, "glDetachShader"); | |
| if (!program) { | |
| return; | |
| } | |
| Shader* shader = GetShaderInfoNotProgram(shader_client_id, "glDetachShader"); | |
| if (!shader) { | |
| return; | |
| } | |
| if (!program->IsShaderAttached(shader)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, | |
| "glDetachShader", "shader not attached to program"); | |
| return; | |
| } | |
| api()->glDetachShaderFn(program->service_id(), shader->service_id()); | |
| program->DetachShader(shader_manager(), shader); | |
| } | |
| void GLES2DecoderImpl::DoValidateProgram(GLuint program_client_id) { | |
| Program* program = GetProgramInfoNotShader( | |
| program_client_id, "glValidateProgram"); | |
| if (!program) { | |
| return; | |
| } | |
| program->Validate(); | |
| } | |
| void GLES2DecoderImpl::GetVertexAttribHelper( | |
| const VertexAttrib* attrib, GLenum pname, GLint* params) { | |
| switch (pname) { | |
| case GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING: | |
| { | |
| Buffer* buffer = attrib->buffer(); | |
| if (buffer && !buffer->IsDeleted()) { | |
| GLuint client_id; | |
| buffer_manager()->GetClientId(buffer->service_id(), &client_id); | |
| *params = client_id; | |
| } | |
| break; | |
| } | |
| case GL_VERTEX_ATTRIB_ARRAY_ENABLED: | |
| *params = attrib->enabled(); | |
| break; | |
| case GL_VERTEX_ATTRIB_ARRAY_SIZE: | |
| *params = attrib->size(); | |
| break; | |
| case GL_VERTEX_ATTRIB_ARRAY_STRIDE: | |
| *params = attrib->gl_stride(); | |
| break; | |
| case GL_VERTEX_ATTRIB_ARRAY_TYPE: | |
| *params = attrib->type(); | |
| break; | |
| case GL_VERTEX_ATTRIB_ARRAY_NORMALIZED: | |
| *params = attrib->normalized(); | |
| break; | |
| case GL_VERTEX_ATTRIB_ARRAY_DIVISOR: | |
| *params = attrib->divisor(); | |
| break; | |
| case GL_VERTEX_ATTRIB_ARRAY_INTEGER: | |
| *params = attrib->integer(); | |
| break; | |
| default: | |
| NOTREACHED(); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoGetSamplerParameterfv(GLuint client_id, | |
| GLenum pname, | |
| GLfloat* params, | |
| GLsizei params_size) { | |
| Sampler* sampler = GetSampler(client_id); | |
| if (!sampler) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, "glGetSamplerParamterfv", "unknown sampler"); | |
| return; | |
| } | |
| api()->glGetSamplerParameterfvFn(sampler->service_id(), pname, params); | |
| } | |
| void GLES2DecoderImpl::DoGetSamplerParameteriv(GLuint client_id, | |
| GLenum pname, | |
| GLint* params, | |
| GLsizei params_size) { | |
| Sampler* sampler = GetSampler(client_id); | |
| if (!sampler) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, "glGetSamplerParamteriv", "unknown sampler"); | |
| return; | |
| } | |
| api()->glGetSamplerParameterivFn(sampler->service_id(), pname, params); | |
| } | |
| void GLES2DecoderImpl::GetTexParameterImpl( | |
| GLenum target, GLenum pname, GLfloat* fparams, GLint* iparams, | |
| const char* function_name) { | |
| TextureRef* texture_ref = texture_manager()->GetTextureInfoForTarget( | |
| &state_, target); | |
| if (!texture_ref) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, function_name, "unknown texture for target"); | |
| return; | |
| } | |
| Texture* texture = texture_ref->texture(); | |
| switch (pname) { | |
| case GL_TEXTURE_BASE_LEVEL: | |
| // Use shadowed value in case it's clamped; also because older MacOSX | |
| // stores the value on int16_t (see https://crbug.com/610153). | |
| if (fparams) { | |
| fparams[0] = static_cast<GLfloat>(texture->unclamped_base_level()); | |
| } else { | |
| iparams[0] = texture->unclamped_base_level(); | |
| } | |
| return; | |
| case GL_TEXTURE_MAX_LEVEL: | |
| // Use shadowed value in case it's clamped; also because older MacOSX | |
| // stores the value on int16_t (see https://crbug.com/610153). | |
| if (fparams) { | |
| fparams[0] = static_cast<GLfloat>(texture->unclamped_max_level()); | |
| } else { | |
| iparams[0] = texture->unclamped_max_level(); | |
| } | |
| return; | |
| case GL_TEXTURE_SWIZZLE_R: | |
| if (fparams) { | |
| fparams[0] = static_cast<GLfloat>(texture->swizzle_r()); | |
| } else { | |
| iparams[0] = texture->swizzle_r(); | |
| } | |
| return; | |
| case GL_TEXTURE_SWIZZLE_G: | |
| if (fparams) { | |
| fparams[0] = static_cast<GLfloat>(texture->swizzle_g()); | |
| } else { | |
| iparams[0] = texture->swizzle_g(); | |
| } | |
| return; | |
| case GL_TEXTURE_SWIZZLE_B: | |
| if (fparams) { | |
| fparams[0] = static_cast<GLfloat>(texture->swizzle_b()); | |
| } else { | |
| iparams[0] = texture->swizzle_b(); | |
| } | |
| return; | |
| case GL_TEXTURE_SWIZZLE_A: | |
| if (fparams) { | |
| fparams[0] = static_cast<GLfloat>(texture->swizzle_a()); | |
| } else { | |
| iparams[0] = texture->swizzle_a(); | |
| } | |
| return; | |
| default: | |
| break; | |
| } | |
| if (fparams) { | |
| api()->glGetTexParameterfvFn(target, pname, fparams); | |
| } else { | |
| api()->glGetTexParameterivFn(target, pname, iparams); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoGetTexParameterfv(GLenum target, | |
| GLenum pname, | |
| GLfloat* params, | |
| GLsizei params_size) { | |
| GetTexParameterImpl(target, pname, params, nullptr, "glGetTexParameterfv"); | |
| } | |
| void GLES2DecoderImpl::DoGetTexParameteriv(GLenum target, | |
| GLenum pname, | |
| GLint* params, | |
| GLsizei params_size) { | |
| GetTexParameterImpl(target, pname, nullptr, params, "glGetTexParameteriv"); | |
| } | |
| template <typename T> | |
| void GLES2DecoderImpl::DoGetVertexAttribImpl( | |
| GLuint index, GLenum pname, T* params) { | |
| VertexAttrib* attrib = state_.vertex_attrib_manager->GetVertexAttrib(index); | |
| if (!attrib) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glGetVertexAttrib", "index out of range"); | |
| return; | |
| } | |
| switch (pname) { | |
| case GL_CURRENT_VERTEX_ATTRIB: | |
| state_.attrib_values[index].GetValues(params); | |
| break; | |
| default: { | |
| GLint value = 0; | |
| GetVertexAttribHelper(attrib, pname, &value); | |
| *params = static_cast<T>(value); | |
| break; | |
| } | |
| } | |
| } | |
| void GLES2DecoderImpl::DoGetVertexAttribfv(GLuint index, | |
| GLenum pname, | |
| GLfloat* params, | |
| GLsizei params_size) { | |
| DoGetVertexAttribImpl<GLfloat>(index, pname, params); | |
| } | |
| void GLES2DecoderImpl::DoGetVertexAttribiv(GLuint index, | |
| GLenum pname, | |
| GLint* params, | |
| GLsizei params_size) { | |
| DoGetVertexAttribImpl<GLint>(index, pname, params); | |
| } | |
| void GLES2DecoderImpl::DoGetVertexAttribIiv(GLuint index, | |
| GLenum pname, | |
| GLint* params, | |
| GLsizei params_size) { | |
| DoGetVertexAttribImpl<GLint>(index, pname, params); | |
| } | |
| void GLES2DecoderImpl::DoGetVertexAttribIuiv(GLuint index, | |
| GLenum pname, | |
| GLuint* params, | |
| GLsizei params_size) { | |
| DoGetVertexAttribImpl<GLuint>(index, pname, params); | |
| } | |
| template <typename T> | |
| bool GLES2DecoderImpl::SetVertexAttribValue( | |
| const char* function_name, GLuint index, const T* value) { | |
| if (index >= state_.attrib_values.size()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "index out of range"); | |
| return false; | |
| } | |
| state_.attrib_values[index].SetValues(value); | |
| return true; | |
| } | |
| void GLES2DecoderImpl::DoVertexAttrib1f(GLuint index, GLfloat v0) { | |
| GLfloat v[4] = { v0, 0.0f, 0.0f, 1.0f, }; | |
| if (SetVertexAttribValue("glVertexAttrib1f", index, v)) { | |
| state_.SetGenericVertexAttribBaseType( | |
| index, SHADER_VARIABLE_FLOAT); | |
| api()->glVertexAttrib1fFn(index, v0); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoVertexAttrib2f(GLuint index, GLfloat v0, GLfloat v1) { | |
| GLfloat v[4] = { v0, v1, 0.0f, 1.0f, }; | |
| if (SetVertexAttribValue("glVertexAttrib2f", index, v)) { | |
| state_.SetGenericVertexAttribBaseType( | |
| index, SHADER_VARIABLE_FLOAT); | |
| api()->glVertexAttrib2fFn(index, v0, v1); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoVertexAttrib3f( | |
| GLuint index, GLfloat v0, GLfloat v1, GLfloat v2) { | |
| GLfloat v[4] = { v0, v1, v2, 1.0f, }; | |
| if (SetVertexAttribValue("glVertexAttrib3f", index, v)) { | |
| state_.SetGenericVertexAttribBaseType( | |
| index, SHADER_VARIABLE_FLOAT); | |
| api()->glVertexAttrib3fFn(index, v0, v1, v2); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoVertexAttrib4f( | |
| GLuint index, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) { | |
| GLfloat v[4] = { v0, v1, v2, v3, }; | |
| if (SetVertexAttribValue("glVertexAttrib4f", index, v)) { | |
| state_.SetGenericVertexAttribBaseType( | |
| index, SHADER_VARIABLE_FLOAT); | |
| api()->glVertexAttrib4fFn(index, v0, v1, v2, v3); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoVertexAttrib1fv(GLuint index, | |
| const volatile GLfloat* v) { | |
| GLfloat t[4] = { v[0], 0.0f, 0.0f, 1.0f, }; | |
| if (SetVertexAttribValue("glVertexAttrib1fv", index, t)) { | |
| state_.SetGenericVertexAttribBaseType( | |
| index, SHADER_VARIABLE_FLOAT); | |
| api()->glVertexAttrib1fvFn(index, t); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoVertexAttrib2fv(GLuint index, | |
| const volatile GLfloat* v) { | |
| GLfloat t[4] = { | |
| v[0], | |
| UNSAFE_TODO(v[1]), | |
| 0.0f, | |
| 1.0f, | |
| }; | |
| if (SetVertexAttribValue("glVertexAttrib2fv", index, t)) { | |
| state_.SetGenericVertexAttribBaseType( | |
| index, SHADER_VARIABLE_FLOAT); | |
| api()->glVertexAttrib2fvFn(index, t); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoVertexAttrib3fv(GLuint index, | |
| const volatile GLfloat* v) { | |
| GLfloat t[4] = { | |
| v[0], | |
| UNSAFE_TODO(v[1]), | |
| UNSAFE_TODO(v[2]), | |
| 1.0f, | |
| }; | |
| if (SetVertexAttribValue("glVertexAttrib3fv", index, t)) { | |
| state_.SetGenericVertexAttribBaseType( | |
| index, SHADER_VARIABLE_FLOAT); | |
| api()->glVertexAttrib3fvFn(index, t); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoVertexAttrib4fv(GLuint index, | |
| const volatile GLfloat* v) { | |
| GLfloat t[4] = {v[0], UNSAFE_TODO(v[1]), UNSAFE_TODO(v[2]), | |
| UNSAFE_TODO(v[3])}; | |
| if (SetVertexAttribValue("glVertexAttrib4fv", index, t)) { | |
| state_.SetGenericVertexAttribBaseType( | |
| index, SHADER_VARIABLE_FLOAT); | |
| api()->glVertexAttrib4fvFn(index, t); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoVertexAttribI4i( | |
| GLuint index, GLint v0, GLint v1, GLint v2, GLint v3) { | |
| GLint v[4] = { v0, v1, v2, v3 }; | |
| if (SetVertexAttribValue("glVertexAttribI4i", index, v)) { | |
| state_.SetGenericVertexAttribBaseType( | |
| index, SHADER_VARIABLE_INT); | |
| api()->glVertexAttribI4iFn(index, v0, v1, v2, v3); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoVertexAttribI4iv(GLuint index, | |
| const volatile GLint* v) { | |
| GLint t[4] = {v[0], UNSAFE_TODO(v[1]), UNSAFE_TODO(v[2]), UNSAFE_TODO(v[3])}; | |
| if (SetVertexAttribValue("glVertexAttribI4iv", index, t)) { | |
| state_.SetGenericVertexAttribBaseType( | |
| index, SHADER_VARIABLE_INT); | |
| api()->glVertexAttribI4ivFn(index, t); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoVertexAttribI4ui( | |
| GLuint index, GLuint v0, GLuint v1, GLuint v2, GLuint v3) { | |
| GLuint v[4] = { v0, v1, v2, v3 }; | |
| if (SetVertexAttribValue("glVertexAttribI4ui", index, v)) { | |
| state_.SetGenericVertexAttribBaseType( | |
| index, SHADER_VARIABLE_UINT); | |
| api()->glVertexAttribI4uiFn(index, v0, v1, v2, v3); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoVertexAttribI4uiv(GLuint index, | |
| const volatile GLuint* v) { | |
| GLuint t[4] = {v[0], UNSAFE_TODO(v[1]), UNSAFE_TODO(v[2]), UNSAFE_TODO(v[3])}; | |
| if (SetVertexAttribValue("glVertexAttribI4uiv", index, t)) { | |
| state_.SetGenericVertexAttribBaseType( | |
| index, SHADER_VARIABLE_UINT); | |
| api()->glVertexAttribI4uivFn(index, t); | |
| } | |
| } | |
| error::Error GLES2DecoderImpl::HandleVertexAttribIPointer( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| if (!feature_info_->IsWebGL2OrES3Context()) | |
| return error::kUnknownCommand; | |
| const volatile gles2::cmds::VertexAttribIPointer& c = | |
| *static_cast<const volatile gles2::cmds::VertexAttribIPointer*>(cmd_data); | |
| GLuint indx = c.indx; | |
| GLint size = c.size; | |
| GLenum type = c.type; | |
| GLsizei stride = c.stride; | |
| GLsizei offset = c.offset; | |
| if (!state_.bound_array_buffer.get() || | |
| state_.bound_array_buffer->IsDeleted()) { | |
| if (offset != 0) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, "glVertexAttribIPointer", "offset != 0"); | |
| return error::kNoError; | |
| } | |
| } | |
| const void* ptr = reinterpret_cast<const void*>(offset); | |
| if (!validators_->vertex_attrib_i_type.IsValid(type)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM("glVertexAttribIPointer", type, "type"); | |
| return error::kNoError; | |
| } | |
| if (size < 1 || size > 4) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glVertexAttribIPointer", "size GL_INVALID_VALUE"); | |
| return error::kNoError; | |
| } | |
| if (indx >= group_->max_vertex_attribs()) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glVertexAttribIPointer", "index out of range"); | |
| return error::kNoError; | |
| } | |
| if (stride < 0) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glVertexAttribIPointer", "stride < 0"); | |
| return error::kNoError; | |
| } | |
| if (stride > 255) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glVertexAttribIPointer", "stride > 255"); | |
| return error::kNoError; | |
| } | |
| if (offset < 0) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glVertexAttribIPointer", "offset < 0"); | |
| return error::kNoError; | |
| } | |
| uint32_t type_size = GLES2Util::GetGLTypeSizeForBuffers(type); | |
| // type_size must be a power of two to use & as optimized modulo. | |
| DCHECK(GLES2Util::IsPOT(type_size)); | |
| if (offset & (type_size - 1)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, | |
| "glVertexAttribIPointer", "offset not valid for type"); | |
| return error::kNoError; | |
| } | |
| if (stride & (type_size - 1)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, | |
| "glVertexAttribIPointer", "stride not valid for type"); | |
| return error::kNoError; | |
| } | |
| GLenum base_type = (type == GL_BYTE || type == GL_SHORT || type == GL_INT) ? | |
| SHADER_VARIABLE_INT : SHADER_VARIABLE_UINT; | |
| state_.vertex_attrib_manager->UpdateAttribBaseTypeAndMask(indx, base_type); | |
| uint32_t group_size = GLES2Util::GetGroupSizeForBufferType(size, type); | |
| DCHECK_LE(group_size, static_cast<uint32_t>(INT_MAX)); | |
| state_.vertex_attrib_manager | |
| ->SetAttribInfo(indx, | |
| state_.bound_array_buffer.get(), | |
| size, | |
| type, | |
| GL_FALSE, | |
| stride, | |
| stride != 0 ? stride : group_size, | |
| offset, | |
| GL_TRUE); | |
| api()->glVertexAttribIPointerFn(indx, size, type, stride, ptr); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleVertexAttribPointer( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::VertexAttribPointer& c = | |
| *static_cast<const volatile gles2::cmds::VertexAttribPointer*>(cmd_data); | |
| GLuint indx = c.indx; | |
| GLint size = c.size; | |
| GLenum type = c.type; | |
| GLboolean normalized = static_cast<GLboolean>(c.normalized); | |
| GLsizei stride = c.stride; | |
| GLsizei offset = c.offset; | |
| if (!state_.bound_array_buffer.get() || | |
| state_.bound_array_buffer->IsDeleted()) { | |
| if (offset != 0) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, "glVertexAttribPointer", "offset != 0"); | |
| return error::kNoError; | |
| } | |
| } | |
| const void* ptr = reinterpret_cast<const void*>(offset); | |
| if (!validators_->vertex_attrib_type.IsValid(type)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM("glVertexAttribPointer", type, "type"); | |
| return error::kNoError; | |
| } | |
| if (size < 1 || size > 4) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glVertexAttribPointer", "size GL_INVALID_VALUE"); | |
| return error::kNoError; | |
| } | |
| if ((type == GL_INT_2_10_10_10_REV || type == GL_UNSIGNED_INT_2_10_10_10_REV) | |
| && size != 4) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, "glVertexAttribPointer", "size != 4"); | |
| return error::kNoError; | |
| } | |
| if (indx >= group_->max_vertex_attribs()) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glVertexAttribPointer", "index out of range"); | |
| return error::kNoError; | |
| } | |
| if (stride < 0) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glVertexAttribPointer", "stride < 0"); | |
| return error::kNoError; | |
| } | |
| if (stride > 255) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glVertexAttribPointer", "stride > 255"); | |
| return error::kNoError; | |
| } | |
| if (offset < 0) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glVertexAttribPointer", "offset < 0"); | |
| return error::kNoError; | |
| } | |
| uint32_t type_size = GLES2Util::GetGLTypeSizeForBuffers(type); | |
| // type_size must be a power of two to use & as optimized modulo. | |
| DCHECK(GLES2Util::IsPOT(type_size)); | |
| if (offset & (type_size - 1)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, | |
| "glVertexAttribPointer", "offset not valid for type"); | |
| return error::kNoError; | |
| } | |
| if (stride & (type_size - 1)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, | |
| "glVertexAttribPointer", "stride not valid for type"); | |
| return error::kNoError; | |
| } | |
| state_.vertex_attrib_manager->UpdateAttribBaseTypeAndMask( | |
| indx, SHADER_VARIABLE_FLOAT); | |
| uint32_t group_size = GLES2Util::GetGroupSizeForBufferType(size, type); | |
| DCHECK_LE(group_size, static_cast<uint32_t>(INT_MAX)); | |
| state_.vertex_attrib_manager | |
| ->SetAttribInfo(indx, | |
| state_.bound_array_buffer.get(), | |
| size, | |
| type, | |
| normalized, | |
| stride, | |
| stride != 0 ? stride : group_size, | |
| offset, | |
| GL_FALSE); | |
| api()->glVertexAttribPointerFn(indx, size, type, normalized, stride, ptr); | |
| return error::kNoError; | |
| } | |
| void GLES2DecoderImpl::DoViewport(GLint x, GLint y, GLsizei width, | |
| GLsizei height) { | |
| state_.viewport_x = x; | |
| state_.viewport_y = y; | |
| state_.viewport_width = std::min(width, viewport_max_width_); | |
| state_.viewport_height = std::min(height, viewport_max_height_); | |
| api()->glViewportFn(x, y, width, height); | |
| } | |
| void GLES2DecoderImpl::DoScissor(GLint x, | |
| GLint y, | |
| GLsizei width, | |
| GLsizei height) { | |
| api()->glScissorFn(x, y, width, height); | |
| } | |
| error::Error GLES2DecoderImpl::HandleVertexAttribDivisorANGLE( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::VertexAttribDivisorANGLE& c = | |
| *static_cast<const volatile gles2::cmds::VertexAttribDivisorANGLE*>( | |
| cmd_data); | |
| if (!features().angle_instanced_arrays) | |
| return error::kUnknownCommand; | |
| GLuint index = c.index; | |
| GLuint divisor = c.divisor; | |
| if (index >= group_->max_vertex_attribs()) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, | |
| "glVertexAttribDivisorANGLE", "index out of range"); | |
| return error::kNoError; | |
| } | |
| state_.vertex_attrib_manager->SetDivisor( | |
| index, | |
| divisor); | |
| api()->glVertexAttribDivisorANGLEFn(index, divisor); | |
| return error::kNoError; | |
| } | |
| template <typename pixel_data_type> | |
| static void WriteAlphaData(void* pixels, | |
| uint32_t row_count, | |
| uint32_t channel_count, | |
| uint32_t alpha_channel_index, | |
| uint32_t unpadded_row_size, | |
| uint32_t padded_row_size, | |
| pixel_data_type alpha_value) { | |
| DCHECK_GT(channel_count, 0U); | |
| DCHECK_EQ(unpadded_row_size % sizeof(pixel_data_type), 0U); | |
| uint32_t unpadded_row_size_in_elements = | |
| unpadded_row_size / sizeof(pixel_data_type); | |
| DCHECK_EQ(padded_row_size % sizeof(pixel_data_type), 0U); | |
| uint32_t padded_row_size_in_elements = | |
| padded_row_size / sizeof(pixel_data_type); | |
| pixel_data_type* dst = | |
| static_cast<pixel_data_type*>(pixels) + alpha_channel_index; | |
| for (uint32_t yy = 0; yy < row_count; ++yy) { | |
| pixel_data_type* end = dst + unpadded_row_size_in_elements; | |
| for (pixel_data_type* d = dst; d < end; d += channel_count) { | |
| *d = alpha_value; | |
| } | |
| dst += padded_row_size_in_elements; | |
| } | |
| } | |
| void GLES2DecoderImpl::FinishReadPixels(GLsizei width, | |
| GLsizei height, | |
| GLsizei format, | |
| GLsizei type, | |
| uint32_t pixels_shm_id, | |
| uint32_t pixels_shm_offset, | |
| uint32_t result_shm_id, | |
| uint32_t result_shm_offset, | |
| GLint pack_alignment, | |
| GLenum read_format, | |
| GLuint buffer) { | |
| TRACE_EVENT0("gpu", "GLES2DecoderImpl::FinishReadPixels"); | |
| typedef cmds::ReadPixels::Result Result; | |
| uint32_t pixels_size; | |
| Result* result = nullptr; | |
| if (result_shm_id != 0) { | |
| result = GetSharedMemoryAs<Result*>(result_shm_id, result_shm_offset, | |
| sizeof(*result)); | |
| if (!result) { | |
| if (buffer != 0) { | |
| api()->glDeleteBuffersARBFn(1, &buffer); | |
| } | |
| return; | |
| } | |
| } | |
| GLES2Util::ComputeImageDataSizes(width, height, 1, format, type, | |
| pack_alignment, &pixels_size, nullptr, | |
| nullptr); | |
| void* pixels = | |
| GetSharedMemoryAs<void*>(pixels_shm_id, pixels_shm_offset, pixels_size); | |
| if (!pixels) { | |
| if (buffer != 0) { | |
| api()->glDeleteBuffersARBFn(1, &buffer); | |
| } | |
| return; | |
| } | |
| if (buffer != 0) { | |
| api()->glBindBufferFn(GL_PIXEL_PACK_BUFFER, buffer); | |
| void* data; | |
| if (features().map_buffer_range) { | |
| data = api()->glMapBufferRangeFn(GL_PIXEL_PACK_BUFFER, 0, pixels_size, | |
| GL_MAP_READ_BIT); | |
| } else { | |
| data = api()->glMapBufferFn(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY); | |
| } | |
| if (!data) { | |
| LOCAL_SET_GL_ERROR(GL_OUT_OF_MEMORY, "glMapBuffer", | |
| "Unable to map memory for readback."); | |
| return; | |
| } | |
| UNSAFE_TODO(memcpy(pixels, data, pixels_size)); | |
| api()->glUnmapBufferFn(GL_PIXEL_PACK_BUFFER); | |
| api()->glBindBufferFn(GL_PIXEL_PACK_BUFFER, | |
| GetServiceId(state_.bound_pixel_pack_buffer.get())); | |
| api()->glDeleteBuffersARBFn(1, &buffer); | |
| } | |
| if (result != nullptr) { | |
| result->success = 1; | |
| } | |
| } | |
| error::Error GLES2DecoderImpl::HandleReadbackARGBImagePixelsINTERNAL( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| NOTIMPLEMENTED_LOG_ONCE(); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleWritePixelsYUVINTERNAL( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| NOTIMPLEMENTED_LOG_ONCE(); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleReadPixels(uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const char* func_name = "glReadPixels"; | |
| const volatile gles2::cmds::ReadPixels& c = | |
| *static_cast<const volatile gles2::cmds::ReadPixels*>(cmd_data); | |
| TRACE_EVENT0("gpu", "GLES2DecoderImpl::HandleReadPixels"); | |
| GLint x = c.x; | |
| GLint y = c.y; | |
| GLsizei width = c.width; | |
| GLsizei height = c.height; | |
| GLenum format = c.format; | |
| GLenum type = c.type; | |
| uint32_t pixels_shm_id = c.pixels_shm_id; | |
| uint32_t pixels_shm_offset = c.pixels_shm_offset; | |
| uint32_t result_shm_id = c.result_shm_id; | |
| uint32_t result_shm_offset = c.result_shm_offset; | |
| GLboolean async = static_cast<GLboolean>(c.async); | |
| if (width < 0 || height < 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name, "dimensions < 0"); | |
| return error::kNoError; | |
| } | |
| typedef cmds::ReadPixels::Result Result; | |
| PixelStoreParams params; | |
| if (pixels_shm_id == 0) { | |
| params = state_.GetPackParams(); | |
| } else { | |
| // When reading into client buffer, we actually set pack parameters to 0 | |
| // (except for alignment) before calling glReadPixels. This makes sure we | |
| // only send back meaningful pixel data to the command buffer client side, | |
| // and the client side will take the responsibility to take the pixels and | |
| // write to the client buffer according to the full ES3 pack parameters. | |
| params.alignment = state_.pack_alignment; | |
| } | |
| uint32_t pixels_size = 0; | |
| uint32_t unpadded_row_size = 0; | |
| uint32_t padded_row_size = 0; | |
| uint32_t skip_size = 0; | |
| uint32_t padding = 0; | |
| if (!GLES2Util::ComputeImageDataSizesES3(width, height, 1, | |
| format, type, | |
| params, | |
| &pixels_size, | |
| &unpadded_row_size, | |
| &padded_row_size, | |
| &skip_size, | |
| &padding)) { | |
| return error::kOutOfBounds; | |
| } | |
| // Defense-in-depth: read_pixel_format may accept enums (e.g. sized norm16 | |
| // internal formats) that GLES2Util::ElementsPerGroup does not recognize, in | |
| // which case ComputeImageDataSizesES3 succeeds with a 0-byte size. If we | |
| // proceeded, GetSharedMemoryAs would validate 0 bytes and we would hand the | |
| // pointer to the native driver, which writes width*height*bpp bytes -> OOB | |
| // write past the renderer's transfer buffer in the GPU process. | |
| if (width > 0 && height > 0 && pixels_size == 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "unsupported format/type combination"); | |
| return error::kNoError; | |
| } | |
| uint8_t* pixels = nullptr; | |
| Buffer* buffer = state_.bound_pixel_pack_buffer.get(); | |
| if (pixels_shm_id == 0) { | |
| if (!buffer) { | |
| return error::kInvalidArguments; | |
| } | |
| if (!buffer_manager()->RequestBufferAccess( | |
| error_state_.get(), buffer, func_name, "pixel pack buffer")) { | |
| return error::kNoError; | |
| } | |
| uint32_t size = 0; | |
| if (!base::CheckAdd(pixels_size + skip_size, pixels_shm_offset) | |
| .AssignIfValid(&size)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name, "size + offset overflow"); | |
| return error::kNoError; | |
| } | |
| if (static_cast<uint32_t>(buffer->size()) < size) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glReadPixels", | |
| "pixel pack buffer is not large enough"); | |
| return error::kNoError; | |
| } | |
| pixels = reinterpret_cast<uint8_t *>(pixels_shm_offset); | |
| UNSAFE_TODO(pixels += skip_size); | |
| } else { | |
| if (buffer) { | |
| return error::kInvalidArguments; | |
| } | |
| DCHECK_EQ(0u, skip_size); | |
| pixels = GetSharedMemoryAs<uint8_t*>( | |
| pixels_shm_id, pixels_shm_offset, pixels_size); | |
| if (!pixels) { | |
| return error::kOutOfBounds; | |
| } | |
| } | |
| Result* result = nullptr; | |
| if (result_shm_id != 0) { | |
| result = GetSharedMemoryAs<Result*>( | |
| result_shm_id, result_shm_offset, sizeof(*result)); | |
| if (!result) { | |
| return error::kOutOfBounds; | |
| } | |
| if (result->success != 0) { | |
| return error::kInvalidArguments; | |
| } | |
| } | |
| if (!validators_->read_pixel_format.IsValid(format)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM(func_name, format, "format"); | |
| return error::kNoError; | |
| } | |
| if (!validators_->read_pixel_type.IsValid(type)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM(func_name, type, "type"); | |
| return error::kNoError; | |
| } | |
| if (!CheckBoundReadFramebufferValid( | |
| func_name, GL_INVALID_FRAMEBUFFER_OPERATION)) { | |
| return error::kNoError; | |
| } | |
| GLenum src_internal_format = GetBoundReadFramebufferInternalFormat(); | |
| if (src_internal_format == 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, "no valid color image"); | |
| return error::kNoError; | |
| } | |
| std::vector<GLenum> accepted_formats; | |
| std::vector<GLenum> accepted_types; | |
| switch (src_internal_format) { | |
| case GL_R8UI: | |
| case GL_R16UI: | |
| case GL_R32UI: | |
| case GL_RG8UI: | |
| case GL_RG16UI: | |
| case GL_RG32UI: | |
| // All the RGB_INTEGER formats are not renderable. | |
| case GL_RGBA8UI: | |
| case GL_RGB10_A2UI: | |
| case GL_RGBA16UI: | |
| case GL_RGBA32UI: | |
| accepted_formats.push_back(GL_RGBA_INTEGER); | |
| accepted_types.push_back(GL_UNSIGNED_INT); | |
| break; | |
| case GL_R8I: | |
| case GL_R16I: | |
| case GL_R32I: | |
| case GL_RG8I: | |
| case GL_RG16I: | |
| case GL_RG32I: | |
| case GL_RGBA8I: | |
| case GL_RGBA16I: | |
| case GL_RGBA32I: | |
| accepted_formats.push_back(GL_RGBA_INTEGER); | |
| accepted_types.push_back(GL_INT); | |
| break; | |
| case GL_RGB10_A2: | |
| accepted_formats.push_back(GL_RGBA); | |
| accepted_types.push_back(GL_UNSIGNED_BYTE); | |
| // Special case with an extra supported format/type. | |
| accepted_formats.push_back(GL_RGBA); | |
| accepted_types.push_back(GL_UNSIGNED_INT_2_10_10_10_REV); | |
| break; | |
| case GL_R16_EXT: | |
| case GL_RG16_EXT: | |
| case GL_RGBA16_EXT: | |
| accepted_formats.push_back(GL_RGBA); | |
| accepted_types.push_back(GL_UNSIGNED_SHORT); | |
| break; | |
| default: | |
| accepted_formats.push_back(GL_RGBA); | |
| { | |
| GLenum src_type = GetBoundReadFramebufferTextureType(); | |
| switch (src_type) { | |
| case GL_HALF_FLOAT: | |
| case GL_HALF_FLOAT_OES: | |
| case GL_FLOAT: | |
| case GL_UNSIGNED_INT_10F_11F_11F_REV: | |
| accepted_types.push_back(GL_FLOAT); | |
| break; | |
| default: | |
| accepted_types.push_back(GL_UNSIGNED_BYTE); | |
| break; | |
| } | |
| } | |
| break; | |
| } | |
| if (!feature_info_->IsWebGLContext()) { | |
| accepted_formats.push_back(GL_BGRA_EXT); | |
| accepted_types.push_back(GL_UNSIGNED_BYTE); | |
| } | |
| DCHECK_EQ(accepted_formats.size(), accepted_types.size()); | |
| bool format_type_acceptable = false; | |
| for (size_t ii = 0; ii < accepted_formats.size(); ++ii) { | |
| if (format == accepted_formats[ii] && type == accepted_types[ii]) { | |
| format_type_acceptable = true; | |
| break; | |
| } | |
| } | |
| if (!format_type_acceptable) { | |
| // format and type are acceptable enums but not guaranteed to be supported | |
| // for this framebuffer. Have to ask gl if they are valid. | |
| GLint preferred_format = 0; | |
| DoGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &preferred_format, 1); | |
| GLint preferred_type = 0; | |
| DoGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &preferred_type, 1); | |
| if (format == static_cast<GLenum>(preferred_format) && | |
| type == static_cast<GLenum>(preferred_type)) { | |
| format_type_acceptable = true; | |
| } | |
| } | |
| if (!format_type_acceptable) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "format and type incompatible with the current read framebuffer"); | |
| return error::kNoError; | |
| } | |
| if (width == 0 || height == 0) { | |
| return error::kNoError; | |
| } | |
| // Get the size of the current fbo or backbuffer. | |
| gfx::Size max_size = GetBoundReadFramebufferSize(); | |
| int32_t max_x; | |
| int32_t max_y; | |
| if (!base::CheckAdd(x, width).AssignIfValid(&max_x) || | |
| !base::CheckAdd(y, height).AssignIfValid(&max_y)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name, "dimensions out of range"); | |
| return error::kNoError; | |
| } | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER(func_name); | |
| GLenum read_format = GetBoundReadFramebufferInternalFormat(); | |
| gfx::Rect rect(x, y, width, height); // Safe before we checked above. | |
| gfx::Rect max_rect(max_size); | |
| if (!max_rect.Contains(rect)) { | |
| rect.Intersect(max_rect); | |
| if (!rect.IsEmpty()) { | |
| std::unique_ptr<ScopedFramebufferCopyBinder> copy_binder; | |
| if (workarounds() | |
| .use_copyteximage2d_instead_of_readpixels_on_multisampled_textures && | |
| framebuffer_state_.bound_read_framebuffer.get() && | |
| framebuffer_state_.bound_read_framebuffer.get() | |
| ->GetReadBufferIsMultisampledTexture()) { | |
| copy_binder = std::make_unique<ScopedFramebufferCopyBinder>(this); | |
| } | |
| if (y < 0) { | |
| UNSAFE_TODO(pixels += static_cast<uint32_t>(-y) * padded_row_size); | |
| } | |
| if (x < 0) { | |
| uint32_t group_size = GLES2Util::ComputeImageGroupSize(format, type); | |
| uint32_t leading_bytes = static_cast<uint32_t>(-x) * group_size; | |
| UNSAFE_TODO(pixels += leading_bytes); | |
| } | |
| bool large_row_length_workaround = | |
| pixels_shm_id == 0 && | |
| workarounds().pack_large_row_length_separately_pack_buffer && | |
| padded_row_size >= 0x10000000u; | |
| if (large_row_length_workaround) { | |
| api()->glPixelStoreiFn(GL_PACK_ROW_LENGTH, 0); | |
| } | |
| for (GLint iy = rect.y(); iy < rect.bottom(); ++iy) { | |
| bool reset_row_length = false; | |
| if (iy + 1 == max_y && pixels_shm_id == 0 && | |
| workarounds().pack_parameters_workaround_with_pack_buffer && | |
| state_.pack_row_length > 0 && state_.pack_row_length < width) { | |
| // Some drivers (for example, Mac AMD) incorrecly limit the last | |
| // row to ROW_LENGTH in this case. | |
| api()->glPixelStoreiFn(GL_PACK_ROW_LENGTH, width); | |
| reset_row_length = true; | |
| } | |
| api()->glReadPixelsFn(rect.x(), iy, rect.width(), 1, format, type, | |
| pixels); | |
| if (reset_row_length) { | |
| api()->glPixelStoreiFn(GL_PACK_ROW_LENGTH, state_.pack_row_length); | |
| } | |
| UNSAFE_TODO(pixels += padded_row_size); | |
| } | |
| if (large_row_length_workaround) { | |
| api()->glPixelStoreiFn(GL_PACK_ROW_LENGTH, state_.pack_row_length); | |
| } | |
| } | |
| } else { | |
| if (async && features().use_async_readpixels && | |
| !state_.bound_pixel_pack_buffer.get()) { | |
| // This workaround isn't implemented in this code path yet. Hopefully it's | |
| // unnecessary. | |
| DCHECK( | |
| !workarounds() | |
| .use_copyteximage2d_instead_of_readpixels_on_multisampled_textures || | |
| !framebuffer_state_.bound_read_framebuffer.get() || | |
| !framebuffer_state_.bound_read_framebuffer.get() | |
| ->GetReadBufferIsMultisampledTexture()); | |
| // To simply the state tracking, we don't go down the async path if | |
| // a PIXEL_PACK_BUFFER is bound (in which case the client can | |
| // implement something similar on their own - all necessary functions | |
| // should be exposed). | |
| GLuint buffer_handle = 0; | |
| api()->glGenBuffersARBFn(1, &buffer_handle); | |
| api()->glBindBufferFn(GL_PIXEL_PACK_BUFFER, buffer_handle); | |
| // For ANGLE client version 2, GL_STREAM_READ is not available. | |
| const GLenum usage_hint = | |
| gl_version_info().is_angle ? GL_STATIC_DRAW : GL_STREAM_READ; | |
| api()->glBufferDataFn(GL_PIXEL_PACK_BUFFER, pixels_size, nullptr, | |
| usage_hint); | |
| GLenum error = api()->glGetErrorFn(); | |
| if (error == GL_NO_ERROR) { | |
| // No need to worry about ES3 pixel pack parameters, because no | |
| // PIXEL_PACK_BUFFER is bound, and all these settings haven't been | |
| // sent to GL. | |
| api()->glReadPixelsFn(x, y, width, height, format, type, 0); | |
| pending_readpixel_fences_.push(FenceCallback()); | |
| WaitForReadPixels(base::BindOnce( | |
| &GLES2DecoderImpl::FinishReadPixels, weak_ptr_factory_.GetWeakPtr(), | |
| width, height, format, type, pixels_shm_id, pixels_shm_offset, | |
| result_shm_id, result_shm_offset, state_.pack_alignment, | |
| read_format, buffer_handle)); | |
| api()->glBindBufferFn(GL_PIXEL_PACK_BUFFER, 0); | |
| return error::kNoError; | |
| } else { | |
| // On error, unbind pack buffer and fall through to sync readpixels | |
| api()->glBindBufferFn(GL_PIXEL_PACK_BUFFER, 0); | |
| api()->glDeleteBuffersARBFn(1, &buffer_handle); | |
| } | |
| } | |
| if (pixels_shm_id == 0 && | |
| workarounds().pack_large_row_length_separately_pack_buffer && | |
| padded_row_size >= 0x10000000u) { | |
| api()->glPixelStoreiFn(GL_PACK_ROW_LENGTH, 0); | |
| for (GLint iy = y; iy < max_y; ++iy) { | |
| api()->glReadPixelsFn(x, iy, width, 1, format, type, pixels); | |
| // SAFETY: maximum bounds were validated to be in-range above. | |
| UNSAFE_BUFFERS(pixels += padded_row_size); | |
| } | |
| api()->glPixelStoreiFn(GL_PACK_ROW_LENGTH, state_.pack_row_length); | |
| } else if (pixels_shm_id == 0 && | |
| workarounds().pack_parameters_workaround_with_pack_buffer) { | |
| // If we ever have a device that needs both of these workarounds, we'll | |
| // need to do some extra work to implement that correctly here. | |
| DCHECK( | |
| !workarounds() | |
| .use_copyteximage2d_instead_of_readpixels_on_multisampled_textures); | |
| if (state_.pack_row_length > 0 && state_.pack_row_length < width) { | |
| // Some drivers (for example, NVidia Linux) reset in this case. | |
| // Some drivers (for example, Mac AMD) incorrecly limit the last | |
| // row to ROW_LENGTH in this case. | |
| api()->glPixelStoreiFn(GL_PACK_ROW_LENGTH, width); | |
| for (GLint iy = y; iy < max_y; ++iy) { | |
| // Need to set PACK_ALIGNMENT for last row. See comment below. | |
| if (iy + 1 == max_y && padding > 0) | |
| api()->glPixelStoreiFn(GL_PACK_ALIGNMENT, 1); | |
| api()->glReadPixelsFn(x, iy, width, 1, format, type, pixels); | |
| if (iy + 1 == max_y && padding > 0) | |
| api()->glPixelStoreiFn(GL_PACK_ALIGNMENT, state_.pack_alignment); | |
| UNSAFE_TODO(pixels += padded_row_size); | |
| } | |
| api()->glPixelStoreiFn(GL_PACK_ROW_LENGTH, state_.pack_row_length); | |
| } else if (padding > 0) { | |
| // Some drivers (for example, NVidia Linux) incorrectly require the | |
| // pack buffer to have padding for the last row. | |
| if (height > 1) | |
| api()->glReadPixelsFn(x, y, width, height - 1, format, type, pixels); | |
| api()->glPixelStoreiFn(GL_PACK_ALIGNMENT, 1); | |
| UNSAFE_TODO(pixels += padded_row_size * (height - 1)); | |
| api()->glReadPixelsFn(x, max_y - 1, width, 1, format, type, pixels); | |
| api()->glPixelStoreiFn(GL_PACK_ALIGNMENT, state_.pack_alignment); | |
| } else { | |
| api()->glReadPixelsFn(x, y, width, height, format, type, pixels); | |
| } | |
| } else if ( | |
| workarounds() | |
| .use_copyteximage2d_instead_of_readpixels_on_multisampled_textures && | |
| framebuffer_state_.bound_read_framebuffer.get() && | |
| framebuffer_state_.bound_read_framebuffer.get() | |
| ->GetReadBufferIsMultisampledTexture()) { | |
| ScopedFramebufferCopyBinder copy_binder(this, x, y, width, height); | |
| api()->glReadPixelsFn(0, 0, width, height, format, type, pixels); | |
| } else { | |
| api()->glReadPixelsFn(x, y, width, height, format, type, pixels); | |
| } | |
| } | |
| if (pixels_shm_id != 0) { | |
| GLenum error = LOCAL_PEEK_GL_ERROR(func_name); | |
| if (error == GL_NO_ERROR) { | |
| if (result) { | |
| result->success = 1; | |
| result->row_length = static_cast<uint32_t>(rect.width()); | |
| result->num_rows = static_cast<uint32_t>(rect.height()); | |
| } | |
| FinishReadPixels(width, height, format, type, pixels_shm_id, | |
| pixels_shm_offset, result_shm_id, result_shm_offset, | |
| state_.pack_alignment, read_format, 0); | |
| } | |
| } | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandlePixelStorei( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::PixelStorei& c = | |
| *static_cast<const volatile gles2::cmds::PixelStorei*>(cmd_data); | |
| GLenum pname = c.pname; | |
| GLint param = c.param; | |
| if (!validators_->pixel_store.IsValid(pname)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM("glPixelStorei", pname, "pname"); | |
| return error::kNoError; | |
| } | |
| switch (pname) { | |
| case GL_PACK_ALIGNMENT: | |
| case GL_UNPACK_ALIGNMENT: | |
| if (!validators_->pixel_store_alignment.IsValid(param)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glPixelStorei", "invalid param"); | |
| return error::kNoError; | |
| } | |
| break; | |
| case GL_PACK_ROW_LENGTH: | |
| case GL_UNPACK_ROW_LENGTH: | |
| case GL_UNPACK_IMAGE_HEIGHT: | |
| if (param < 0) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glPixelStorei", "invalid param"); | |
| return error::kNoError; | |
| } | |
| break; | |
| case GL_PACK_SKIP_PIXELS: | |
| case GL_PACK_SKIP_ROWS: | |
| case GL_UNPACK_SKIP_PIXELS: | |
| case GL_UNPACK_SKIP_ROWS: | |
| case GL_UNPACK_SKIP_IMAGES: | |
| // All SKIP parameters are handled on the client side and should never | |
| // be passed to the service side. | |
| return error::kInvalidArguments; | |
| default: | |
| break; | |
| } | |
| // For alignment parameters, we always apply them. | |
| // For other parameters, we don't apply them if no buffer is bound at | |
| // PIXEL_PACK or PIXEL_UNPACK. We will handle pack and unpack according to | |
| // the user specified parameters on the client side. | |
| switch (pname) { | |
| case GL_PACK_ROW_LENGTH: | |
| if (state_.bound_pixel_pack_buffer.get()) | |
| api()->glPixelStoreiFn(pname, param); | |
| break; | |
| case GL_UNPACK_ROW_LENGTH: | |
| case GL_UNPACK_IMAGE_HEIGHT: | |
| if (state_.bound_pixel_unpack_buffer.get()) | |
| api()->glPixelStoreiFn(pname, param); | |
| break; | |
| default: | |
| api()->glPixelStoreiFn(pname, param); | |
| break; | |
| } | |
| switch (pname) { | |
| case GL_PACK_ALIGNMENT: | |
| state_.pack_alignment = param; | |
| break; | |
| case GL_PACK_ROW_LENGTH: | |
| state_.pack_row_length = param; | |
| break; | |
| case GL_UNPACK_ALIGNMENT: | |
| state_.unpack_alignment = param; | |
| break; | |
| case GL_UNPACK_ROW_LENGTH: | |
| state_.unpack_row_length = param; | |
| break; | |
| case GL_UNPACK_IMAGE_HEIGHT: | |
| state_.unpack_image_height = param; | |
| break; | |
| default: | |
| // Validation should have prevented us from getting here. | |
| NOTREACHED(); | |
| } | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::GetAttribLocationHelper( | |
| GLuint client_id, | |
| uint32_t location_shm_id, | |
| uint32_t location_shm_offset, | |
| const std::string& name_str) { | |
| if (!StringIsValidForGLES(name_str)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glGetAttribLocation", "Invalid character"); | |
| return error::kNoError; | |
| } | |
| Program* program = GetProgramInfoNotShader( | |
| client_id, "glGetAttribLocation"); | |
| if (!program) { | |
| return error::kNoError; | |
| } | |
| if (!program->IsValid()) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, "glGetAttribLocation", "program not linked"); | |
| return error::kNoError; | |
| } | |
| GLint* location = GetSharedMemoryAs<GLint*>( | |
| location_shm_id, location_shm_offset, sizeof(GLint)); | |
| if (!location) { | |
| return error::kOutOfBounds; | |
| } | |
| // Check that the client initialized the result. | |
| if (*location != -1) { | |
| return error::kInvalidArguments; | |
| } | |
| *location = program->GetAttribLocation(name_str); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetAttribLocation( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::GetAttribLocation& c = | |
| *static_cast<const volatile gles2::cmds::GetAttribLocation*>(cmd_data); | |
| Bucket* bucket = GetBucket(c.name_bucket_id); | |
| if (!bucket) { | |
| return error::kInvalidArguments; | |
| } | |
| std::string name_str; | |
| if (!bucket->GetAsString(&name_str)) { | |
| return error::kInvalidArguments; | |
| } | |
| return GetAttribLocationHelper( | |
| c.program, c.location_shm_id, c.location_shm_offset, name_str); | |
| } | |
| error::Error GLES2DecoderImpl::GetUniformLocationHelper( | |
| GLuint client_id, | |
| uint32_t location_shm_id, | |
| uint32_t location_shm_offset, | |
| const std::string& name_str) { | |
| if (!StringIsValidForGLES(name_str)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glGetUniformLocation", "Invalid character"); | |
| return error::kNoError; | |
| } | |
| Program* program = GetProgramInfoNotShader( | |
| client_id, "glGetUniformLocation"); | |
| if (!program) { | |
| return error::kNoError; | |
| } | |
| if (!program->IsValid()) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, "glGetUniformLocation", "program not linked"); | |
| return error::kNoError; | |
| } | |
| GLint* location = GetSharedMemoryAs<GLint*>( | |
| location_shm_id, location_shm_offset, sizeof(GLint)); | |
| if (!location) { | |
| return error::kOutOfBounds; | |
| } | |
| // Check that the client initialized the result. | |
| if (*location != -1) { | |
| return error::kInvalidArguments; | |
| } | |
| *location = program->GetUniformFakeLocation(name_str); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetUniformLocation( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::GetUniformLocation& c = | |
| *static_cast<const volatile gles2::cmds::GetUniformLocation*>(cmd_data); | |
| Bucket* bucket = GetBucket(c.name_bucket_id); | |
| if (!bucket) { | |
| return error::kInvalidArguments; | |
| } | |
| std::string name_str; | |
| if (!bucket->GetAsString(&name_str)) { | |
| return error::kInvalidArguments; | |
| } | |
| return GetUniformLocationHelper( | |
| c.program, c.location_shm_id, c.location_shm_offset, name_str); | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetUniformIndices( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| if (!feature_info_->IsWebGL2OrES3Context()) | |
| return error::kUnknownCommand; | |
| const volatile gles2::cmds::GetUniformIndices& c = | |
| *static_cast<const volatile gles2::cmds::GetUniformIndices*>(cmd_data); | |
| Bucket* bucket = GetBucket(c.names_bucket_id); | |
| if (!bucket) { | |
| return error::kInvalidArguments; | |
| } | |
| std::optional<std::vector<std::string_view>> names = bucket->GetAsStrings(); | |
| if (!names.has_value() || names->empty()) { | |
| return error::kInvalidArguments; | |
| } | |
| const GLsizei count = static_cast<GLsizei>(names->size()); | |
| std::vector<const char*> name_ptrs; | |
| name_ptrs.reserve(names->size()); | |
| for (std::string_view name : *names) { | |
| name_ptrs.push_back(name.data()); | |
| } | |
| typedef cmds::GetUniformIndices::Result Result; | |
| uint32_t checked_size = 0; | |
| if (!Result::ComputeSize(count).AssignIfValid(&checked_size)) { | |
| return error::kOutOfBounds; | |
| } | |
| Result* result = GetSharedMemoryAs<Result*>( | |
| c.indices_shm_id, c.indices_shm_offset, checked_size); | |
| GLuint* indices = result ? result->GetData() : nullptr; | |
| if (indices == nullptr) { | |
| return error::kOutOfBounds; | |
| } | |
| // Check that the client initialized the result. | |
| if (result->size != 0) { | |
| return error::kInvalidArguments; | |
| } | |
| Program* program = GetProgramInfoNotShader(c.program, "glGetUniformIndices"); | |
| if (!program) { | |
| return error::kNoError; | |
| } | |
| GLuint service_id = program->service_id(); | |
| GLint link_status = GL_FALSE; | |
| api()->glGetProgramivFn(service_id, GL_LINK_STATUS, &link_status); | |
| if (link_status != GL_TRUE) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, | |
| "glGetUniformIndices", "program not linked"); | |
| return error::kNoError; | |
| } | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER("GetUniformIndices"); | |
| api()->glGetUniformIndicesFn(service_id, count, name_ptrs.data(), indices); | |
| GLenum error = api()->glGetErrorFn(); | |
| if (error == GL_NO_ERROR) { | |
| result->SetNumResults(count); | |
| } else { | |
| LOCAL_SET_GL_ERROR(error, "GetUniformIndices", ""); | |
| } | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::GetFragDataLocationHelper( | |
| GLuint client_id, | |
| uint32_t location_shm_id, | |
| uint32_t location_shm_offset, | |
| const std::string& name_str) { | |
| const char kFunctionName[] = "glGetFragDataLocation"; | |
| GLint* location = GetSharedMemoryAs<GLint*>( | |
| location_shm_id, location_shm_offset, sizeof(GLint)); | |
| if (!location) { | |
| return error::kOutOfBounds; | |
| } | |
| // Check that the client initialized the result. | |
| if (*location != -1) { | |
| return error::kInvalidArguments; | |
| } | |
| Program* program = GetProgramInfoNotShader(client_id, kFunctionName); | |
| if (!program) { | |
| return error::kNoError; | |
| } | |
| if (!program->IsValid()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, kFunctionName, | |
| "program not linked"); | |
| return error::kNoError; | |
| } | |
| *location = program->GetFragDataLocation(name_str); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetFragDataLocation( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| if (!feature_info_->IsWebGL2OrES3Context()) | |
| return error::kUnknownCommand; | |
| const volatile gles2::cmds::GetFragDataLocation& c = | |
| *static_cast<const volatile gles2::cmds::GetFragDataLocation*>(cmd_data); | |
| Bucket* bucket = GetBucket(c.name_bucket_id); | |
| if (!bucket) { | |
| return error::kInvalidArguments; | |
| } | |
| std::string name_str; | |
| if (!bucket->GetAsString(&name_str)) { | |
| return error::kInvalidArguments; | |
| } | |
| return GetFragDataLocationHelper( | |
| c.program, c.location_shm_id, c.location_shm_offset, name_str); | |
| } | |
| error::Error GLES2DecoderImpl::GetFragDataIndexHelper( | |
| GLuint program_id, | |
| uint32_t index_shm_id, | |
| uint32_t index_shm_offset, | |
| const std::string& name_str) { | |
| const char kFunctionName[] = "glGetFragDataIndexEXT"; | |
| GLint* index = | |
| GetSharedMemoryAs<GLint*>(index_shm_id, index_shm_offset, sizeof(GLint)); | |
| if (!index) { | |
| return error::kOutOfBounds; | |
| } | |
| // Check that the client initialized the result. | |
| if (*index != -1) { | |
| return error::kInvalidArguments; | |
| } | |
| Program* program = GetProgramInfoNotShader(program_id, kFunctionName); | |
| if (!program) { | |
| return error::kNoError; | |
| } | |
| if (!program->IsValid()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, kFunctionName, | |
| "program not linked"); | |
| return error::kNoError; | |
| } | |
| *index = program->GetFragDataIndex(name_str); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetFragDataIndexEXT( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| if (!features().ext_blend_func_extended) { | |
| return error::kUnknownCommand; | |
| } | |
| const volatile gles2::cmds::GetFragDataIndexEXT& c = | |
| *static_cast<const volatile gles2::cmds::GetFragDataIndexEXT*>(cmd_data); | |
| Bucket* bucket = GetBucket(c.name_bucket_id); | |
| if (!bucket) { | |
| return error::kInvalidArguments; | |
| } | |
| std::string name_str; | |
| if (!bucket->GetAsString(&name_str)) { | |
| return error::kInvalidArguments; | |
| } | |
| return GetFragDataIndexHelper(c.program, c.index_shm_id, c.index_shm_offset, | |
| name_str); | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetUniformBlockIndex( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| if (!feature_info_->IsWebGL2OrES3Context()) | |
| return error::kUnknownCommand; | |
| const volatile gles2::cmds::GetUniformBlockIndex& c = | |
| *static_cast<const volatile gles2::cmds::GetUniformBlockIndex*>(cmd_data); | |
| Bucket* bucket = GetBucket(c.name_bucket_id); | |
| if (!bucket) { | |
| return error::kInvalidArguments; | |
| } | |
| std::string name_str; | |
| if (!bucket->GetAsString(&name_str)) { | |
| return error::kInvalidArguments; | |
| } | |
| GLuint* index = GetSharedMemoryAs<GLuint*>( | |
| c.index_shm_id, c.index_shm_offset, sizeof(GLuint)); | |
| if (!index) { | |
| return error::kOutOfBounds; | |
| } | |
| // Check that the client initialized the result. | |
| if (*index != GL_INVALID_INDEX) { | |
| return error::kInvalidArguments; | |
| } | |
| Program* program = GetProgramInfoNotShader( | |
| c.program, "glGetUniformBlockIndex"); | |
| if (!program) { | |
| return error::kNoError; | |
| } | |
| *index = | |
| api()->glGetUniformBlockIndexFn(program->service_id(), name_str.c_str()); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetString(uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::GetString& c = | |
| *static_cast<const volatile gles2::cmds::GetString*>(cmd_data); | |
| GLenum name = static_cast<GLenum>(c.name); | |
| if (!validators_->string_type.IsValid(name)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM("glGetString", name, "name"); | |
| return error::kNoError; | |
| } | |
| const char* str = nullptr; | |
| std::string extensions; | |
| switch (name) { | |
| case GL_VERSION: | |
| str = GetServiceVersionString(feature_info_.get()); | |
| break; | |
| case GL_SHADING_LANGUAGE_VERSION: | |
| str = GetServiceShadingLanguageVersionString(feature_info_.get()); | |
| break; | |
| case GL_EXTENSIONS: { | |
| gfx::ExtensionSet extension_set = feature_info_->extensions(); | |
| // For WebGL contexts, strip out shader extensions if they have not | |
| // been enabled on WebGL1 or no longer exist (become core) in WebGL2. | |
| if (feature_info_->IsWebGLContext()) { | |
| if (!derivatives_explicitly_enabled_) | |
| extension_set.erase(kOESDerivativeExtension); | |
| if (!fbo_render_mipmap_explicitly_enabled_) | |
| extension_set.erase(kOESFboRenderMipmapExtension); | |
| if (!frag_depth_explicitly_enabled_) | |
| extension_set.erase(kEXTFragDepthExtension); | |
| if (!draw_buffers_explicitly_enabled_) | |
| extension_set.erase(kEXTDrawBuffersExtension); | |
| if (!shader_texture_lod_explicitly_enabled_) | |
| extension_set.erase(kEXTShaderTextureLodExtension); | |
| if (!multi_draw_explicitly_enabled_) | |
| extension_set.erase(kWEBGLMultiDrawExtension); | |
| if (!draw_instanced_base_vertex_base_instance_explicitly_enabled_) | |
| extension_set.erase( | |
| kWEBGLDrawInstancedBaseVertexBaseInstanceExtension); | |
| if (!multi_draw_instanced_base_vertex_base_instance_explicitly_enabled_) | |
| extension_set.erase( | |
| kWEBGLMultiDrawInstancedBaseVertexBaseInstanceExtension); | |
| } | |
| extensions = gfx::MakeExtensionString(extension_set); | |
| str = extensions.c_str(); | |
| break; | |
| } | |
| default: | |
| str = reinterpret_cast<const char*>(api()->glGetStringFn(name)); | |
| break; | |
| } | |
| Bucket* bucket = CreateBucket(c.bucket_id); | |
| bucket->SetFromString(str); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleBufferData(uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::BufferData& c = | |
| *static_cast<const volatile gles2::cmds::BufferData*>(cmd_data); | |
| GLenum target = static_cast<GLenum>(c.target); | |
| GLsizeiptr size = static_cast<GLsizeiptr>(c.size); | |
| uint32_t data_shm_id = static_cast<uint32_t>(c.data_shm_id); | |
| uint32_t data_shm_offset = static_cast<uint32_t>(c.data_shm_offset); | |
| GLenum usage = static_cast<GLenum>(c.usage); | |
| const void* data = nullptr; | |
| if (data_shm_id != 0 || data_shm_offset != 0) { | |
| data = GetSharedMemoryAs<const void*>(data_shm_id, data_shm_offset, size); | |
| if (!data) { | |
| return error::kOutOfBounds; | |
| } | |
| } | |
| buffer_manager()->ValidateAndDoBufferData(&state_, error_state_.get(), target, | |
| size, data, usage); | |
| return error::kNoError; | |
| } | |
| void GLES2DecoderImpl::DoBufferSubData( | |
| GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid * data) { | |
| // Just delegate it. Some validation is actually done before this. | |
| buffer_manager()->ValidateAndDoBufferSubData(&state_, error_state_.get(), | |
| target, offset, size, data); | |
| } | |
| bool GLES2DecoderImpl::ClearLevel(Texture* texture, | |
| unsigned target, | |
| int level, | |
| unsigned format, | |
| unsigned type, | |
| int xoffset, | |
| int yoffset, | |
| int width, | |
| int height) { | |
| TRACE_EVENT0("gpu", "GLES2DecoderImpl::ClearLevel"); | |
| DCHECK(target != GL_TEXTURE_3D && target != GL_TEXTURE_2D_ARRAY && | |
| target != GL_TEXTURE_EXTERNAL_OES); | |
| uint32_t channels = GLES2Util::GetChannelsForFormat(format); | |
| bool must_use_gl_clear = false; | |
| if ((channels & GLES2Util::kDepth) != 0 && | |
| feature_info_->feature_flags().angle_depth_texture && | |
| feature_info_->gl_version_info().is_es2) { | |
| // It's a depth format and ANGLE doesn't allow texImage2D or texSubImage2D | |
| // on depth formats in ES2. | |
| must_use_gl_clear = true; | |
| } | |
| uint32_t size; | |
| uint32_t padded_row_size; | |
| if (!GLES2Util::ComputeImageDataSizes(width, height, 1, format, type, | |
| state_.unpack_alignment, &size, nullptr, | |
| &padded_row_size)) { | |
| return false; | |
| } | |
| // Prefer to do the clear using a glClear call unless the clear is very small | |
| // (less than 64x64, or one page). Calls to TexSubImage2D on large textures | |
| // can take hundreds of milliseconds because of slow uploads on macOS. Do this | |
| // only on macOS because clears are buggy on other drivers. | |
| // https://crbug.com/848952 (slow uploads on macOS) | |
| // https://crbug.com/883276 (buggy clears on Android) | |
| bool prefer_use_gl_clear = false; | |
| if (must_use_gl_clear || prefer_use_gl_clear) { | |
| if (ClearLevelUsingGL(texture, channels, target, level, xoffset, yoffset, | |
| width, height)) { | |
| return true; | |
| } | |
| } | |
| if (must_use_gl_clear) | |
| return false; | |
| TRACE_EVENT1("gpu", "Clear using TexSubImage2D", "size", size); | |
| int tile_height; | |
| const uint32_t kMaxZeroSize = 1024 * 1024 * 4; | |
| if (size > kMaxZeroSize) { | |
| if (kMaxZeroSize < padded_row_size) { | |
| // That'd be an awfully large texture. | |
| return false; | |
| } | |
| // We should never have a large total size with a zero row size. | |
| DCHECK_GT(padded_row_size, 0U); | |
| tile_height = kMaxZeroSize / padded_row_size; | |
| if (!GLES2Util::ComputeImageDataSizes(width, tile_height, 1, format, type, | |
| state_.unpack_alignment, &size, | |
| nullptr, nullptr)) { | |
| return false; | |
| } | |
| } else { | |
| tile_height = height; | |
| } | |
| api()->glBindTextureFn(texture->target(), texture->service_id()); | |
| { | |
| // Add extra scope to destroy zero and the object it owns right | |
| // after its usage. | |
| // Assumes the size has already been checked. | |
| auto zero = base::HeapArray<char>::WithSize(size); | |
| ScopedPixelUnpackState reset_restore(&state_); | |
| GLint y = 0; | |
| while (y < height) { | |
| GLint h = y + tile_height > height ? height - y : tile_height; | |
| api()->glTexSubImage2DFn( | |
| target, level, xoffset, yoffset + y, width, h, | |
| TextureManager::AdjustTexFormat(feature_info_.get(), format), type, | |
| zero.data()); | |
| y += tile_height; | |
| } | |
| } | |
| TextureRef* bound_texture = | |
| texture_manager()->GetTextureInfoForTarget(&state_, texture->target()); | |
| api()->glBindTextureFn(texture->target(), | |
| bound_texture ? bound_texture->service_id() : 0); | |
| DCHECK(glGetError() == GL_NO_ERROR); | |
| return true; | |
| } | |
| bool GLES2DecoderImpl::ClearLevelUsingGL(Texture* texture, | |
| uint32_t channels, | |
| unsigned target, | |
| int level, | |
| int xoffset, | |
| int yoffset, | |
| int width, | |
| int height) { | |
| TRACE_EVENT0("gpu", "GLES2DecoderImpl::ClearLevelUsingGL"); | |
| GLenum fb_target = GetDrawFramebufferTarget(); | |
| GLuint fb = 0; | |
| api()->glGenFramebuffersEXTFn(1, &fb); | |
| BindFramebuffer(fb_target, fb); | |
| bool have_color = (channels & GLES2Util::kRGBA) != 0; | |
| if (have_color) { | |
| api()->glFramebufferTexture2DEXTFn(fb_target, GL_COLOR_ATTACHMENT0, target, | |
| texture->service_id(), level); | |
| } | |
| bool have_depth = (channels & GLES2Util::kDepth) != 0; | |
| if (have_depth) { | |
| api()->glFramebufferTexture2DEXTFn(fb_target, GL_DEPTH_ATTACHMENT, target, | |
| texture->service_id(), level); | |
| } | |
| bool have_stencil = (channels & GLES2Util::kStencil) != 0; | |
| if (have_stencil) { | |
| api()->glFramebufferTexture2DEXTFn(fb_target, GL_STENCIL_ATTACHMENT, target, | |
| texture->service_id(), level); | |
| } | |
| // Attempt to do the clear only if the framebuffer is complete. ANGLE | |
| // promises a depth only attachment ok. | |
| bool result = false; | |
| if (api()->glCheckFramebufferStatusEXTFn(fb_target) == | |
| GL_FRAMEBUFFER_COMPLETE) { | |
| state_.SetDeviceColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); | |
| api()->glClearColorFn(0.0, 0.0, 0.0, 0.0); | |
| api()->glClearStencilFn(0); | |
| state_.SetDeviceStencilMaskSeparate(GL_FRONT, kDefaultStencilMask); | |
| state_.SetDeviceStencilMaskSeparate(GL_BACK, kDefaultStencilMask); | |
| api()->glClearDepthFn(1.0f); | |
| state_.SetDeviceDepthMask(GL_TRUE); | |
| state_.SetDeviceCapabilityState(GL_SCISSOR_TEST, true); | |
| api()->glScissorFn(xoffset, yoffset, width, height); | |
| ClearDeviceWindowRectangles(); | |
| api()->glClearFn((have_color ? GL_COLOR_BUFFER_BIT : 0) | | |
| (have_depth ? GL_DEPTH_BUFFER_BIT : 0) | | |
| (have_stencil ? GL_STENCIL_BUFFER_BIT : 0)); | |
| result = true; | |
| } | |
| RestoreClearState(); | |
| // Restore the previous framebuffer binding *before* deleting the temporary | |
| // FBO. Some Imagination/PowerVR drivers retain an internal reference to the | |
| // previously-bound FBO across bind transitions; deleting it while bound and | |
| // then rebinding can dereference freed driver state. See the | |
| // ensure_previous_framebuffer_not_deleted workaround. | |
| Framebuffer* framebuffer = GetFramebufferInfoForTarget(fb_target); | |
| GLuint fb_service_id = | |
| framebuffer ? framebuffer->service_id() : GetBackbufferServiceId(); | |
| BindFramebuffer(fb_target, fb_service_id); | |
| api()->glDeleteFramebuffersEXTFn(1, &fb); | |
| return result; | |
| } | |
| bool GLES2DecoderImpl::ClearCompressedTextureLevel(Texture* texture, | |
| unsigned target, | |
| int level, | |
| unsigned format, | |
| int width, | |
| int height) { | |
| DCHECK(target != GL_TEXTURE_3D && target != GL_TEXTURE_2D_ARRAY); | |
| // This code path can only be called if the texture was originally | |
| // allocated via TexStorage2D. Note that TexStorage2D is exposed | |
| // internally for ES 2.0 contexts, but compressed texture support is | |
| // not part of that exposure. | |
| DCHECK(feature_info_->IsWebGL2OrES3Context()); | |
| GLsizei bytes_required = 0; | |
| if (!GetCompressedTexSizeInBytes("ClearCompressedTextureLevel", width, height, | |
| 1, format, &bytes_required, | |
| error_state_.get())) { | |
| return false; | |
| } | |
| TRACE_EVENT1("gpu", "GLES2DecoderImpl::ClearCompressedTextureLevel", | |
| "bytes_required", bytes_required); | |
| bool cleared = true; | |
| api()->glBindBufferFn(GL_PIXEL_UNPACK_BUFFER, 0); | |
| { | |
| // Add extra scope to destroy zero and the object it owns right | |
| // after its usage. | |
| auto zero = base::HeapArray<char>::WithSize(bytes_required); | |
| bool reset_base_level = workarounds().reset_base_level_for_astc_image && | |
| IsASTCFormat(format) && texture->base_level() != 0; | |
| api()->glBindTextureFn(texture->target(), texture->service_id()); | |
| if (reset_base_level) { | |
| api()->glTexParameteriFn(texture->target(), GL_TEXTURE_BASE_LEVEL, 0); | |
| } | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER("ClearCompressedTextureLevel"); | |
| // This zero-fill sources from CPU memory, but the client's unpack | |
| // geometry is still resident in the real driver and must not influence a | |
| // compressed upload; see the comment in DoCompressedTexImage. | |
| ScopedCompressedUnpackStateScrub scrub(&state_); | |
| api()->glCompressedTexSubImage2DFn(target, level, 0, 0, width, height, | |
| format, zero.size(), zero.data()); | |
| // Some compressed formats are not supported by CompressedTexSubImage* | |
| // in some circumstances. If the driver rejects the call, report the failure | |
| // to clear so the caller does not mark it cleared (which would let a draw | |
| // sample uninitialized GPU memory). | |
| if (LOCAL_PEEK_GL_ERROR("ClearCompressedTextureLevel") != GL_NO_ERROR) { | |
| cleared = false; | |
| } | |
| if (reset_base_level) { | |
| api()->glTexParameteriFn(texture->target(), GL_TEXTURE_BASE_LEVEL, | |
| texture->base_level()); | |
| } | |
| } | |
| TextureRef* bound_texture = | |
| texture_manager()->GetTextureInfoForTarget(&state_, texture->target()); | |
| api()->glBindTextureFn(texture->target(), | |
| bound_texture ? bound_texture->service_id() : 0); | |
| Buffer* bound_buffer = buffer_manager()->GetBufferInfoForTarget( | |
| &state_, GL_PIXEL_UNPACK_BUFFER); | |
| if (bound_buffer) { | |
| api()->glBindBufferFn(GL_PIXEL_UNPACK_BUFFER, bound_buffer->service_id()); | |
| } | |
| return cleared; | |
| } | |
| bool GLES2DecoderImpl::ClearCompressedTextureLevel3D(Texture* texture, | |
| unsigned target, | |
| int level, | |
| unsigned format, | |
| int width, | |
| int height, | |
| int depth) { | |
| DCHECK(target == GL_TEXTURE_3D || target == GL_TEXTURE_2D_ARRAY); | |
| // This code path can only be called if the texture was originally | |
| // allocated via TexStorage3D. Note that TexStorage3D is exposed | |
| // internally for ES 2.0 contexts, but compressed texture support is | |
| // not part of that exposure. | |
| DCHECK(feature_info_->IsWebGL2OrES3Context()); | |
| GLsizei bytes_required = 0; | |
| if (!GetCompressedTexSizeInBytes("ClearCompressedTextureLevel3D", width, | |
| height, depth, format, &bytes_required, | |
| error_state_.get())) { | |
| return false; | |
| } | |
| TRACE_EVENT1("gpu", "GLES2DecoderImpl::ClearCompressedTextureLevel3D", | |
| "bytes_required", bytes_required); | |
| bool cleared = true; | |
| api()->glBindBufferFn(GL_PIXEL_UNPACK_BUFFER, 0); | |
| { | |
| // Add extra scope to destroy zero and the object it owns right | |
| // after its usage. | |
| auto zero = base::HeapArray<char>::WithSize(bytes_required); | |
| bool reset_base_level = workarounds().reset_base_level_for_astc_image && | |
| IsASTCFormat(format) && texture->base_level() != 0; | |
| api()->glBindTextureFn(texture->target(), texture->service_id()); | |
| if (reset_base_level) { | |
| api()->glTexParameteriFn(texture->target(), GL_TEXTURE_BASE_LEVEL, 0); | |
| } | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER("ClearCompressedTextureLevel3D"); | |
| // This zero-fill sources from CPU memory, but the client's unpack | |
| // geometry is still resident in the real driver and must not influence a | |
| // compressed upload; see the comment in DoCompressedTexImage. | |
| ScopedCompressedUnpackStateScrub scrub(&state_); | |
| api()->glCompressedTexSubImage3DFn(target, level, 0, 0, 0, width, height, | |
| depth, format, zero.size(), zero.data()); | |
| // Some compressed formats are not supported by CompressedTexSubImage* | |
| // in some circumstances. If the driver rejects the call, report the failure | |
| // to clear so the caller does not mark it cleared (which would let a draw | |
| // sample uninitialized GPU memory). | |
| if (LOCAL_PEEK_GL_ERROR("ClearCompressedTextureLevel3D") != GL_NO_ERROR) { | |
| cleared = false; | |
| } | |
| if (reset_base_level) { | |
| api()->glTexParameteriFn(texture->target(), GL_TEXTURE_BASE_LEVEL, | |
| texture->base_level()); | |
| } | |
| } | |
| TextureRef* bound_texture = | |
| texture_manager()->GetTextureInfoForTarget(&state_, texture->target()); | |
| api()->glBindTextureFn(texture->target(), | |
| bound_texture ? bound_texture->service_id() : 0); | |
| Buffer* bound_buffer = | |
| buffer_manager()->GetBufferInfoForTarget(&state_, GL_PIXEL_UNPACK_BUFFER); | |
| if (bound_buffer) { | |
| api()->glBindBufferFn(GL_PIXEL_UNPACK_BUFFER, bound_buffer->service_id()); | |
| } | |
| return cleared; | |
| } | |
| bool GLES2DecoderImpl::IsCompressedTextureFormat(unsigned format) { | |
| return feature_info_->validators()->compressed_texture_format.IsValid( | |
| format); | |
| } | |
| bool GLES2DecoderImpl::ClearLevel3D(Texture* texture, | |
| unsigned target, | |
| int level, | |
| unsigned format, | |
| unsigned type, | |
| int width, | |
| int height, | |
| int depth) { | |
| DCHECK(target == GL_TEXTURE_3D || target == GL_TEXTURE_2D_ARRAY); | |
| DCHECK(feature_info_->IsWebGL2OrES3Context()); | |
| if (width == 0 || height == 0 || depth == 0) | |
| return true; | |
| uint32_t size; | |
| uint32_t padded_row_size; | |
| uint32_t padding; | |
| // Here we use unpack buffer to upload zeros into the texture, one layer | |
| // at a time. | |
| // We only take into consideration UNPACK_ALIGNMENT, and clear other unpack | |
| // parameters if necessary before TexSubImage3D calls. | |
| PixelStoreParams params; | |
| params.alignment = state_.unpack_alignment; | |
| if (!GLES2Util::ComputeImageDataSizesES3(width, height, depth, | |
| format, type, | |
| params, | |
| &size, | |
| nullptr, | |
| &padded_row_size, | |
| nullptr, | |
| &padding)) { | |
| return false; | |
| } | |
| const uint32_t kMaxZeroSize = 1024 * 1024 * 2; | |
| uint32_t buffer_size; | |
| std::vector<TexSubCoord3D> subs; | |
| if (size < kMaxZeroSize) { | |
| // Case 1: one TexSubImage3D call clears the entire 3D texture. | |
| buffer_size = size; | |
| subs.push_back(TexSubCoord3D(0, 0, 0, width, height, depth)); | |
| } else { | |
| uint32_t size_per_layer; | |
| if (!base::CheckMul(padded_row_size, height) | |
| .AssignIfValid(&size_per_layer)) { | |
| return false; | |
| } | |
| if (size_per_layer < kMaxZeroSize) { | |
| // Case 2: Each TexSubImage3D call clears 1 or more layers. | |
| uint32_t depth_step = kMaxZeroSize / size_per_layer; | |
| uint32_t num_of_slices = depth / depth_step; | |
| if (num_of_slices * depth_step < static_cast<uint32_t>(depth)) | |
| num_of_slices++; | |
| DCHECK_LT(0u, num_of_slices); | |
| buffer_size = size_per_layer * depth_step; | |
| int depth_of_last_slice = depth - (num_of_slices - 1) * depth_step; | |
| DCHECK_LT(0, depth_of_last_slice); | |
| for (uint32_t ii = 0; ii < num_of_slices; ++ii) { | |
| int depth_ii = | |
| (ii + 1 == num_of_slices ? depth_of_last_slice : depth_step); | |
| subs.push_back( | |
| TexSubCoord3D(0, 0, depth_step * ii, width, height, depth_ii)); | |
| } | |
| } else { | |
| // Case 3: Multiple TexSubImage3D calls clear 1 layer. | |
| if (kMaxZeroSize < padded_row_size) { | |
| // That'd be an awfully large texture. | |
| return false; | |
| } | |
| uint32_t height_step = kMaxZeroSize / padded_row_size; | |
| uint32_t num_of_slices = height / height_step; | |
| if (num_of_slices * height_step < static_cast<uint32_t>(height)) | |
| num_of_slices++; | |
| DCHECK_LT(0u, num_of_slices); | |
| buffer_size = padded_row_size * height_step; | |
| int height_of_last_slice = height - (num_of_slices - 1) * height_step; | |
| DCHECK_LT(0, height_of_last_slice); | |
| for (int zz = 0; zz < depth; ++zz) { | |
| for (uint32_t ii = 0; ii < num_of_slices; ++ii) { | |
| int height_ii = | |
| (ii + 1 == num_of_slices ? height_of_last_slice : height_step); | |
| subs.push_back( | |
| TexSubCoord3D(0, height_step * ii, zz, width, height_ii, 1)); | |
| } | |
| } | |
| } | |
| } | |
| TRACE_EVENT1("gpu", "GLES2DecoderImpl::ClearLevel3D", "size", size); | |
| // Drain any pre-existing driver errors so the post-upload check below only | |
| // reflects errors generated by this clear sequence. | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER("glClearLevel3D"); | |
| { | |
| ScopedPixelUnpackState reset_restore(&state_); | |
| GLuint buffer_id = 0; | |
| api()->glGenBuffersARBFn(1, &buffer_id); | |
| api()->glBindBufferFn(GL_PIXEL_UNPACK_BUFFER, buffer_id); | |
| { | |
| // Include padding as some drivers incorrectly requires padding for the | |
| // last row. | |
| buffer_size += padding; | |
| auto zero = base::HeapArray<char>::WithSize(buffer_size); | |
| // TODO(zmo): Consider glMapBufferRange instead. | |
| api()->glBufferDataFn(GL_PIXEL_UNPACK_BUFFER, zero.size(), zero.data(), | |
| GL_STATIC_DRAW); | |
| } | |
| api()->glBindTextureFn(texture->target(), texture->service_id()); | |
| for (size_t ii = 0; ii < subs.size(); ++ii) { | |
| api()->glTexSubImage3DFn(target, level, subs[ii].xoffset, | |
| subs[ii].yoffset, subs[ii].zoffset, | |
| subs[ii].width, subs[ii].height, subs[ii].depth, | |
| format, type, nullptr); | |
| } | |
| api()->glDeleteBuffersARBFn(1, &buffer_id); | |
| } | |
| TextureRef* bound_texture = | |
| texture_manager()->GetTextureInfoForTarget(&state_, texture->target()); | |
| api()->glBindTextureFn(texture->target(), | |
| bound_texture ? bound_texture->service_id() : 0); | |
| // glBufferData() may fail with GL_OUT_OF_MEMORY under VRAM pressure, leaving | |
| // the PBO without a data store and causing every glTexSubImage3D above to | |
| // fail GL_INVALID_OPERATION without writing zeros. Do not report the level as | |
| // cleared in that case; routing the error through PeekGLError also ensures | |
| // OnOutOfMemoryError() fires so lose_context_when_out_of_memory_ applies. | |
| if (LOCAL_PEEK_GL_ERROR("glClearLevel3D") != GL_NO_ERROR) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| namespace { | |
| bool CheckETCFormatSupport(const FeatureInfo& feature_info) { | |
| const gl::GLVersionInfo& version_info = feature_info.gl_version_info(); | |
| return version_info.IsAtLeastGLES(3, 0); | |
| } | |
| using CompressedFormatSupportCheck = bool (*)(const FeatureInfo&); | |
| using CompressedFormatDecompressionFunction = void (*)(size_t width, | |
| size_t height, | |
| size_t depth, | |
| const uint8_t* input, | |
| size_t inputRowPitch, | |
| size_t inputDepthPitch, | |
| uint8_t* output, | |
| size_t outputRowPitch, | |
| size_t outputDepthPitch); | |
| struct CompressedFormatInfo { | |
| GLenum format; | |
| uint32_t block_size; | |
| uint32_t bytes_per_block; | |
| CompressedFormatSupportCheck support_check; | |
| CompressedFormatDecompressionFunction decompression_function; | |
| GLenum decompressed_internal_format; | |
| GLenum decompressed_format; | |
| GLenum decompressed_type; | |
| }; | |
| const auto kCompressedFormatInfoArray = std::to_array<CompressedFormatInfo>({ | |
| { | |
| GL_COMPRESSED_R11_EAC, | |
| 4, | |
| 8, | |
| CheckETCFormatSupport, | |
| angle::LoadEACR11ToR8, | |
| GL_R8, | |
| GL_RED, | |
| GL_UNSIGNED_BYTE, | |
| }, | |
| { | |
| GL_COMPRESSED_SIGNED_R11_EAC, | |
| 4, | |
| 8, | |
| CheckETCFormatSupport, | |
| angle::LoadEACR11SToR8, | |
| GL_R8_SNORM, | |
| GL_RED, | |
| GL_BYTE, | |
| }, | |
| { | |
| GL_COMPRESSED_RG11_EAC, | |
| 4, | |
| 16, | |
| CheckETCFormatSupport, | |
| angle::LoadEACRG11ToRG8, | |
| GL_RG8, | |
| GL_RG, | |
| GL_UNSIGNED_BYTE, | |
| }, | |
| { | |
| GL_COMPRESSED_SIGNED_RG11_EAC, | |
| 4, | |
| 16, | |
| CheckETCFormatSupport, | |
| angle::LoadEACRG11SToRG8, | |
| GL_RG8_SNORM, | |
| GL_RG, | |
| GL_BYTE, | |
| }, | |
| { | |
| GL_COMPRESSED_RGB8_ETC2, | |
| 4, | |
| 8, | |
| CheckETCFormatSupport, | |
| angle::LoadETC2RGB8ToRGBA8, | |
| GL_RGBA8, | |
| GL_RGBA, | |
| GL_UNSIGNED_BYTE, | |
| }, | |
| { | |
| GL_COMPRESSED_SRGB8_ETC2, | |
| 4, | |
| 8, | |
| CheckETCFormatSupport, | |
| angle::LoadETC2SRGB8ToRGBA8, | |
| GL_SRGB8_ALPHA8_EXT, | |
| GL_SRGB_ALPHA_EXT, | |
| GL_UNSIGNED_BYTE, | |
| }, | |
| { | |
| GL_COMPRESSED_RGBA8_ETC2_EAC, | |
| 4, | |
| 16, | |
| CheckETCFormatSupport, | |
| angle::LoadETC2RGBA8ToRGBA8, | |
| GL_RGBA8, | |
| GL_RGBA, | |
| GL_UNSIGNED_BYTE, | |
| }, | |
| { | |
| GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2, | |
| 4, | |
| 8, | |
| CheckETCFormatSupport, | |
| angle::LoadETC2RGB8A1ToRGBA8, | |
| GL_RGBA8, | |
| GL_RGBA, | |
| GL_UNSIGNED_BYTE, | |
| }, | |
| { | |
| GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC, | |
| 4, | |
| 16, | |
| CheckETCFormatSupport, | |
| angle::LoadETC2SRGBA8ToSRGBA8, | |
| GL_SRGB8_ALPHA8_EXT, | |
| GL_SRGB_ALPHA_EXT, | |
| GL_UNSIGNED_BYTE, | |
| }, | |
| { | |
| GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2, | |
| 4, | |
| 8, | |
| CheckETCFormatSupport, | |
| angle::LoadETC2SRGB8A1ToRGBA8, | |
| GL_SRGB8_ALPHA8_EXT, | |
| GL_SRGB_ALPHA_EXT, | |
| GL_UNSIGNED_BYTE, | |
| }, | |
| }); | |
| const CompressedFormatInfo* GetCompressedFormatInfo(GLenum format) { | |
| for (size_t i = 0; i < std::size(kCompressedFormatInfoArray); i++) { | |
| if (kCompressedFormatInfoArray[i].format == format) { | |
| return &kCompressedFormatInfoArray[i]; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| uint32_t GetCompressedFormatRowPitch(const CompressedFormatInfo& info, | |
| uint32_t width) { | |
| uint32_t num_blocks_wide = (width + info.block_size - 1) / info.block_size; | |
| return num_blocks_wide * info.bytes_per_block; | |
| } | |
| uint32_t GetCompressedFormatDepthPitch(const CompressedFormatInfo& info, | |
| uint32_t width, | |
| uint32_t height) { | |
| uint32_t num_blocks_high = (height + info.block_size - 1) / info.block_size; | |
| return num_blocks_high * GetCompressedFormatRowPitch(info, width); | |
| } | |
| base::HeapArray<uint8_t> DecompressTextureData(const ContextState& state, | |
| const CompressedFormatInfo& info, | |
| uint32_t width, | |
| uint32_t height, | |
| uint32_t depth, | |
| GLsizei image_size, | |
| const void* data) { | |
| auto* api = state.api(); | |
| uint32_t output_pixel_size = GLES2Util::ComputeImageGroupSize( | |
| info.decompressed_format, info.decompressed_type); | |
| auto decompressed_data = base::HeapArray<uint8_t>::Uninit( | |
| output_pixel_size * width * height * depth); | |
| // If a PBO is bound, map it to decompress the data. | |
| const void* input_data = data; | |
| if (state.bound_pixel_unpack_buffer) { | |
| input_data = api->glMapBufferRangeFn(GL_PIXEL_UNPACK_BUFFER, | |
| reinterpret_cast<GLintptr>(data), | |
| image_size, GL_MAP_READ_BIT); | |
| if (input_data == nullptr) { | |
| LOG(ERROR) << "Failed to map pixel unpack buffer."; | |
| return base::HeapArray<uint8_t>(); | |
| } | |
| } | |
| DCHECK_NE(input_data, nullptr); | |
| info.decompression_function( | |
| width, height, depth, static_cast<const uint8_t*>(input_data), | |
| GetCompressedFormatRowPitch(info, width), | |
| GetCompressedFormatDepthPitch(info, width, height), | |
| decompressed_data.data(), output_pixel_size * width, | |
| output_pixel_size * width * height); | |
| if (state.bound_pixel_unpack_buffer) { | |
| if (api->glUnmapBufferFn(GL_PIXEL_UNPACK_BUFFER) != GL_TRUE) { | |
| LOG(ERROR) << "glUnmapBuffer unexpectedly returned GL_FALSE"; | |
| return base::HeapArray<uint8_t>(); | |
| } | |
| } | |
| return decompressed_data; | |
| } | |
| } // anonymous namespace. | |
| bool GLES2DecoderImpl::ValidateCompressedTexFuncData(const char* function_name, | |
| GLsizei width, | |
| GLsizei height, | |
| GLsizei depth, | |
| GLenum format, | |
| GLsizei size, | |
| const GLvoid* data) { | |
| GLsizei bytes_required = 0; | |
| if (!GetCompressedTexSizeInBytes(function_name, width, height, depth, format, | |
| &bytes_required, error_state_.get())) { | |
| return false; | |
| } | |
| if (size != bytes_required) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, function_name, "size is not correct for dimensions"); | |
| return false; | |
| } | |
| Buffer* buffer = state_.bound_pixel_unpack_buffer.get(); | |
| if (buffer && | |
| !buffer_manager()->RequestBufferAccess( | |
| error_state_.get(), buffer, reinterpret_cast<GLintptr>(data), | |
| static_cast<GLsizeiptr>(bytes_required), function_name, | |
| "pixel unpack buffer")) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool GLES2DecoderImpl::ValidateCompressedTexDimensions( | |
| const char* function_name, GLenum target, GLint level, | |
| GLsizei width, GLsizei height, GLsizei depth, GLenum format) { | |
| const char* error_message = ""; | |
| if (!::gpu::gles2::ValidateCompressedTexDimensions( | |
| target, level, width, height, depth, format, &error_message)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, error_message); | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool GLES2DecoderImpl::ValidateCompressedTexSubDimensions( | |
| const char* function_name, | |
| GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, | |
| GLsizei width, GLsizei height, GLsizei depth, GLenum format, | |
| Texture* texture) { | |
| const char* error_message = ""; | |
| if (!::gpu::gles2::ValidateCompressedTexSubDimensions( | |
| target, level, xoffset, yoffset, zoffset, width, height, depth, | |
| format, texture, &error_message)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, error_message); | |
| return false; | |
| } | |
| return true; | |
| } | |
| error::Error GLES2DecoderImpl::HandleCompressedTexImage2DBucket( | |
| uint32_t immediate_data_size, const volatile void* cmd_data) { | |
| const volatile gles2::cmds::CompressedTexImage2DBucket& c = | |
| *static_cast<const volatile gles2::cmds::CompressedTexImage2DBucket*>( | |
| cmd_data); | |
| GLenum target = static_cast<GLenum>(c.target); | |
| GLint level = static_cast<GLint>(c.level); | |
| GLenum internal_format = static_cast<GLenum>(c.internalformat); | |
| GLsizei width = static_cast<GLsizei>(c.width); | |
| GLsizei height = static_cast<GLsizei>(c.height); | |
| GLuint bucket_id = static_cast<GLuint>(c.bucket_id); | |
| GLint border = static_cast<GLint>(c.border); | |
| if (state_.bound_pixel_unpack_buffer.get()) { | |
| return error::kInvalidArguments; | |
| } | |
| Bucket* bucket = GetBucket(bucket_id); | |
| if (!bucket) | |
| return error::kInvalidArguments; | |
| uint32_t image_size = bucket->size(); | |
| base::span<uint8_t> data = bucket->GetDataAsByteSpan(0, image_size); | |
| DCHECK_EQ(data.size(), image_size); | |
| return DoCompressedTexImage(target, level, internal_format, width, height, 1, | |
| border, image_size, data.data(), | |
| ContextState::k2D); | |
| } | |
| error::Error GLES2DecoderImpl::HandleCompressedTexImage2D( | |
| uint32_t immediate_data_size, const volatile void* cmd_data) { | |
| const volatile gles2::cmds::CompressedTexImage2D& c = | |
| *static_cast<const volatile gles2::cmds::CompressedTexImage2D*>(cmd_data); | |
| GLenum target = static_cast<GLenum>(c.target); | |
| GLint level = static_cast<GLint>(c.level); | |
| GLenum internal_format = static_cast<GLenum>(c.internalformat); | |
| GLsizei width = static_cast<GLsizei>(c.width); | |
| GLsizei height = static_cast<GLsizei>(c.height); | |
| GLint border = static_cast<GLint>(c.border); | |
| GLsizei image_size = static_cast<GLsizei>(c.imageSize); | |
| uint32_t data_shm_id = c.data_shm_id; | |
| uint32_t data_shm_offset = c.data_shm_offset; | |
| const void* data; | |
| if (state_.bound_pixel_unpack_buffer.get()) { | |
| if (data_shm_id) { | |
| return error::kInvalidArguments; | |
| } | |
| data = reinterpret_cast<const void*>(data_shm_offset); | |
| } else { | |
| if (!data_shm_id && data_shm_offset) { | |
| return error::kInvalidArguments; | |
| } | |
| data = GetSharedMemoryAs<const void*>( | |
| data_shm_id, data_shm_offset, image_size); | |
| } | |
| return DoCompressedTexImage(target, level, internal_format, width, height, 1, | |
| border, image_size, data, ContextState::k2D); | |
| } | |
| error::Error GLES2DecoderImpl::HandleCompressedTexImage3DBucket( | |
| uint32_t immediate_data_size, const volatile void* cmd_data) { | |
| if (!feature_info_->IsWebGL2OrES3Context()) | |
| return error::kUnknownCommand; | |
| const volatile gles2::cmds::CompressedTexImage3DBucket& c = | |
| *static_cast<const volatile gles2::cmds::CompressedTexImage3DBucket*>( | |
| cmd_data); | |
| GLenum target = static_cast<GLenum>(c.target); | |
| GLint level = static_cast<GLint>(c.level); | |
| GLenum internal_format = static_cast<GLenum>(c.internalformat); | |
| GLsizei width = static_cast<GLsizei>(c.width); | |
| GLsizei height = static_cast<GLsizei>(c.height); | |
| GLsizei depth = static_cast<GLsizei>(c.depth); | |
| GLuint bucket_id = static_cast<GLuint>(c.bucket_id); | |
| GLint border = static_cast<GLint>(c.border); | |
| if (state_.bound_pixel_unpack_buffer.get()) { | |
| return error::kInvalidArguments; | |
| } | |
| Bucket* bucket = GetBucket(bucket_id); | |
| if (!bucket) | |
| return error::kInvalidArguments; | |
| uint32_t image_size = bucket->size(); | |
| base::span<uint8_t> data = bucket->GetDataAsByteSpan(0, image_size); | |
| DCHECK_EQ(data.size(), image_size); | |
| return DoCompressedTexImage(target, level, internal_format, width, height, | |
| depth, border, image_size, data.data(), | |
| ContextState::k3D); | |
| } | |
| error::Error GLES2DecoderImpl::HandleCompressedTexImage3D( | |
| uint32_t immediate_data_size, const volatile void* cmd_data) { | |
| if (!feature_info_->IsWebGL2OrES3Context()) | |
| return error::kUnknownCommand; | |
| const volatile gles2::cmds::CompressedTexImage3D& c = | |
| *static_cast<const volatile gles2::cmds::CompressedTexImage3D*>(cmd_data); | |
| GLenum target = static_cast<GLenum>(c.target); | |
| GLint level = static_cast<GLint>(c.level); | |
| GLenum internal_format = static_cast<GLenum>(c.internalformat); | |
| GLsizei width = static_cast<GLsizei>(c.width); | |
| GLsizei height = static_cast<GLsizei>(c.height); | |
| GLsizei depth = static_cast<GLsizei>(c.depth); | |
| GLint border = static_cast<GLint>(c.border); | |
| GLsizei image_size = static_cast<GLsizei>(c.imageSize); | |
| uint32_t data_shm_id = c.data_shm_id; | |
| uint32_t data_shm_offset = c.data_shm_offset; | |
| const void* data; | |
| if (state_.bound_pixel_unpack_buffer.get()) { | |
| if (data_shm_id) { | |
| return error::kInvalidArguments; | |
| } | |
| data = reinterpret_cast<const void*>(data_shm_offset); | |
| } else { | |
| if (!data_shm_id && data_shm_offset) { | |
| return error::kInvalidArguments; | |
| } | |
| data = GetSharedMemoryAs<const void*>( | |
| data_shm_id, data_shm_offset, image_size); | |
| } | |
| return DoCompressedTexImage(target, level, internal_format, width, height, | |
| depth, border, image_size, data, | |
| ContextState::k3D); | |
| } | |
| error::Error GLES2DecoderImpl::HandleCompressedTexSubImage3DBucket( | |
| uint32_t immediate_data_size, const volatile void* cmd_data) { | |
| if (!feature_info_->IsWebGL2OrES3Context()) | |
| return error::kUnknownCommand; | |
| const volatile gles2::cmds::CompressedTexSubImage3DBucket& c = | |
| *static_cast<const volatile gles2::cmds::CompressedTexSubImage3DBucket*>( | |
| cmd_data); | |
| GLenum target = static_cast<GLenum>(c.target); | |
| GLint level = static_cast<GLint>(c.level); | |
| GLint xoffset = static_cast<GLint>(c.xoffset); | |
| GLint yoffset = static_cast<GLint>(c.yoffset); | |
| GLint zoffset = static_cast<GLint>(c.zoffset); | |
| GLsizei width = static_cast<GLsizei>(c.width); | |
| GLsizei height = static_cast<GLsizei>(c.height); | |
| GLsizei depth = static_cast<GLsizei>(c.depth); | |
| GLenum format = static_cast<GLenum>(c.format); | |
| GLuint bucket_id = static_cast<GLuint>(c.bucket_id); | |
| if (state_.bound_pixel_unpack_buffer.get()) { | |
| return error::kInvalidArguments; | |
| } | |
| Bucket* bucket = GetBucket(bucket_id); | |
| if (!bucket) | |
| return error::kInvalidArguments; | |
| uint32_t image_size = bucket->size(); | |
| base::span<uint8_t> data = bucket->GetDataAsByteSpan(0, image_size); | |
| DCHECK_EQ(data.size(), image_size); | |
| return DoCompressedTexSubImage(target, level, xoffset, yoffset, zoffset, | |
| width, height, depth, format, image_size, | |
| data.data(), ContextState::k3D); | |
| } | |
| error::Error GLES2DecoderImpl::HandleCompressedTexSubImage3D( | |
| uint32_t immediate_data_size, const volatile void* cmd_data) { | |
| if (!feature_info_->IsWebGL2OrES3Context()) | |
| return error::kUnknownCommand; | |
| const volatile gles2::cmds::CompressedTexSubImage3D& c = | |
| *static_cast<const volatile gles2::cmds::CompressedTexSubImage3D*>( | |
| cmd_data); | |
| GLenum target = static_cast<GLenum>(c.target); | |
| GLint level = static_cast<GLint>(c.level); | |
| GLint xoffset = static_cast<GLint>(c.xoffset); | |
| GLint yoffset = static_cast<GLint>(c.yoffset); | |
| GLint zoffset = static_cast<GLint>(c.zoffset); | |
| GLsizei width = static_cast<GLsizei>(c.width); | |
| GLsizei height = static_cast<GLsizei>(c.height); | |
| GLsizei depth = static_cast<GLsizei>(c.depth); | |
| GLenum format = static_cast<GLenum>(c.format); | |
| GLsizei image_size = static_cast<GLsizei>(c.imageSize); | |
| uint32_t data_shm_id = c.data_shm_id; | |
| uint32_t data_shm_offset = c.data_shm_offset; | |
| const void* data; | |
| if (state_.bound_pixel_unpack_buffer.get()) { | |
| if (data_shm_id) { | |
| return error::kInvalidArguments; | |
| } | |
| data = reinterpret_cast<const void*>(data_shm_offset); | |
| } else { | |
| if (!data_shm_id && data_shm_offset) { | |
| return error::kInvalidArguments; | |
| } | |
| data = GetSharedMemoryAs<const void*>( | |
| data_shm_id, data_shm_offset, image_size); | |
| } | |
| return DoCompressedTexSubImage(target, level, xoffset, yoffset, zoffset, | |
| width, height, depth, format, image_size, | |
| data, ContextState::k3D); | |
| } | |
| error::Error GLES2DecoderImpl::DoCompressedTexImage( | |
| GLenum target, GLint level, GLenum internal_format, GLsizei width, | |
| GLsizei height, GLsizei depth, GLint border, GLsizei image_size, | |
| const void* data, ContextState::Dimension dimension) { | |
| const char* func_name; | |
| if (dimension == ContextState::k2D) { | |
| func_name = "glCompressedTexImage2D"; | |
| if (!validators_->texture_target.IsValid(target)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM(func_name, target, "target"); | |
| return error::kNoError; | |
| } | |
| // TODO(ccameron): Add a separate texture from |texture_target| for | |
| // [Compressed]Tex[Sub]Image2D and related functions. | |
| // http://crbug.com/536854 | |
| if (target == GL_TEXTURE_RECTANGLE_ANGLE) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM(func_name, target, "target"); | |
| return error::kNoError; | |
| } | |
| } else { | |
| DCHECK_EQ(ContextState::k3D, dimension); | |
| func_name = "glCompressedTexImage3D"; | |
| if (!validators_->texture_3_d_target.IsValid(target)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM(func_name, target, "target"); | |
| return error::kNoError; | |
| } | |
| } | |
| if (!validators_->compressed_texture_format.IsValid(internal_format)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM( | |
| func_name, internal_format, "internalformat"); | |
| return error::kNoError; | |
| } | |
| if (image_size < 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name, "imageSize < 0"); | |
| return error::kNoError; | |
| } | |
| TextureRef* texture_ref = texture_manager()->GetTextureInfoForTarget( | |
| &state_, target); | |
| if (!texture_ref) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, func_name, "no texture bound at target"); | |
| return error::kNoError; | |
| } | |
| Texture* texture = texture_ref->texture(); | |
| if (!texture_manager()->ValidForTextureTarget(texture, level, width, height, | |
| depth) || | |
| border != 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name, "dimensions out of range"); | |
| return error::kNoError; | |
| } | |
| if (texture->IsImmutable()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, "texture is immutable"); | |
| return error::kNoError; | |
| } | |
| if (!ValidateCompressedTexDimensions(func_name, target, level, | |
| width, height, depth, internal_format) || | |
| !ValidateCompressedTexFuncData(func_name, width, height, depth, | |
| internal_format, image_size, data)) { | |
| return error::kNoError; | |
| } | |
| if (texture->IsAttachedToFramebuffer()) { | |
| framebuffer_state_.clear_state_dirty = true; | |
| } | |
| base::HeapArray<int8_t> zero; | |
| if (!state_.bound_pixel_unpack_buffer && !data) { | |
| // Memory is already zero-initialized: | |
| zero = base::HeapArray<int8_t>::WithSize(image_size); | |
| data = zero.data(); | |
| } | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER(func_name); | |
| const CompressedFormatInfo* format_info = | |
| GetCompressedFormatInfo(internal_format); | |
| if (format_info != nullptr && !format_info->support_check(*feature_info_)) { | |
| auto decompressed_data = DecompressTextureData( | |
| state_, *format_info, width, height, depth, image_size, data); | |
| if (decompressed_data.empty()) { | |
| MarkContextLost(error::kGuilty); | |
| group_->LoseContexts(error::kInnocent); | |
| return error::kLostContext; | |
| } | |
| ScopedPixelUnpackState reset_restore(&state_); | |
| if (dimension == ContextState::k2D) { | |
| bool handled = false; | |
| if (workarounds().upload_oversized_mip_levels_via_unpack_buffer && | |
| target == GL_TEXTURE_2D && level > 0 && | |
| !state_.bound_pixel_unpack_buffer) { | |
| GLsizei level0_width = 0; | |
| GLsizei level0_height = 0; | |
| GLsizei level0_depth = 0; | |
| if (texture->GetLevelSize(target, 0, &level0_width, &level0_height, | |
| &level0_depth) && | |
| level0_width > 0 && level0_height > 0) { | |
| const int slot_w = std::max( | |
| 1, static_cast<int>( | |
| std::bit_ceil(static_cast<uint32_t>(level0_width))) >> | |
| level); | |
| const int slot_h = std::max( | |
| 1, static_cast<int>( | |
| std::bit_ceil(static_cast<uint32_t>(level0_height))) >> | |
| level); | |
| if (width > slot_w || height > slot_h) { | |
| GLuint scratch = 0; | |
| api()->glGenBuffersARBFn(1, &scratch); | |
| api()->glBindBufferFn(GL_PIXEL_UNPACK_BUFFER, scratch); | |
| if (!decompressed_data.empty()) { | |
| api()->glBufferDataFn( | |
| GL_PIXEL_UNPACK_BUFFER, | |
| decompressed_data.size() * sizeof(decompressed_data[0]), | |
| decompressed_data.data(), GL_STREAM_DRAW); | |
| } | |
| api()->glTexImage2DFn( | |
| target, level, format_info->decompressed_internal_format, width, | |
| height, border, format_info->decompressed_format, | |
| format_info->decompressed_type, nullptr); | |
| api()->glBindBufferFn(GL_PIXEL_UNPACK_BUFFER, 0); | |
| api()->glDeleteBuffersARBFn(1, &scratch); | |
| handled = true; | |
| } | |
| } | |
| } | |
| if (!handled) { | |
| api()->glTexImage2DFn( | |
| target, level, format_info->decompressed_internal_format, width, | |
| height, border, format_info->decompressed_format, | |
| format_info->decompressed_type, decompressed_data.data()); | |
| } | |
| } else { | |
| api()->glTexImage3DFn( | |
| target, level, format_info->decompressed_internal_format, width, | |
| height, depth, border, format_info->decompressed_format, | |
| format_info->decompressed_type, decompressed_data.data()); | |
| } | |
| } else { | |
| // Per OpenGL ES 3.2, "All pixel storage modes are ignored when decoding | |
| // a compressed texture image" (sec. 8.7; SubImage data is "interpreted | |
| // as though ... provided to CompressedTexImage*", and sec. 8.4.1 defines | |
| // unpack state as pertaining only to TexImage*, TexSubImage*, and | |
| // ReadPixels), so UNPACK_ROW_LENGTH and UNPACK_IMAGE_HEIGHT must never | |
| // influence a compressed upload. With a PIXEL_UNPACK buffer bound, | |
| // however, the renderer-supplied values are resident in the real driver | |
| // (see HandlePixelStorei), so they are scrubbed to their initial values | |
| // (0, table 8.1) around the dispatch. ANGLE's frontend has applied this | |
| // same neutralization to its compressed entry points since 2021 | |
| // (crbug.com/1267496), so the passthrough decoder is already protected. | |
| // The other compressed dispatch sites (DoCompressedTexSubImage and the | |
| // ClearCompressedTextureLevel paths) reference this comment. | |
| // https://crbug.com/562279351 | |
| ScopedCompressedUnpackStateScrub scrub(&state_); | |
| bool reset_base_level = workarounds().reset_base_level_for_astc_image && | |
| IsASTCFormat(internal_format) && | |
| texture->base_level() != 0; | |
| if (reset_base_level) { | |
| api()->glTexParameteriFn(texture->target(), GL_TEXTURE_BASE_LEVEL, 0); | |
| } | |
| if (dimension == ContextState::k2D) { | |
| bool handled = false; | |
| if (workarounds().upload_oversized_mip_levels_via_unpack_buffer && | |
| target == GL_TEXTURE_2D && level > 0 && | |
| !state_.bound_pixel_unpack_buffer) { | |
| GLsizei level0_width = 0; | |
| GLsizei level0_height = 0; | |
| GLsizei level0_depth = 0; | |
| if (texture->GetLevelSize(target, 0, &level0_width, &level0_height, | |
| &level0_depth) && | |
| level0_width > 0 && level0_height > 0) { | |
| const gfx::Vector2d block_size = | |
| GetCompressedTexBlockDimensions(internal_format); | |
| const int expected_width = std::max(1, level0_width >> level); | |
| const int expected_height = std::max(1, level0_height >> level); | |
| const int expected_blocks_x = | |
| (expected_width + block_size.x() - 1) / block_size.x(); | |
| const int expected_blocks_y = | |
| (expected_height + block_size.y() - 1) / block_size.y(); | |
| const int incoming_blocks_x = | |
| (width + block_size.x() - 1) / block_size.x(); | |
| const int incoming_blocks_y = | |
| (height + block_size.y() - 1) / block_size.y(); | |
| if (incoming_blocks_x > expected_blocks_x || | |
| incoming_blocks_y > expected_blocks_y) { | |
| GLuint scratch = 0; | |
| api()->glGenBuffersARBFn(1, &scratch); | |
| api()->glBindBufferFn(GL_PIXEL_UNPACK_BUFFER, scratch); | |
| // Regardless of whether the user supplied data (data != | |
| // nullptr), the pixel unpack buffer must be allocated | |
| // with the expected amount of data. | |
| if (image_size > 0) { | |
| api()->glBufferDataFn(GL_PIXEL_UNPACK_BUFFER, image_size, data, | |
| GL_STREAM_DRAW); | |
| } | |
| api()->glCompressedTexImage2DFn(target, level, internal_format, | |
| width, height, border, image_size, | |
| nullptr); | |
| api()->glBindBufferFn(GL_PIXEL_UNPACK_BUFFER, 0); | |
| api()->glDeleteBuffersARBFn(1, &scratch); | |
| handled = true; | |
| } | |
| } | |
| } | |
| if (!handled) { | |
| api()->glCompressedTexImage2DFn(target, level, internal_format, width, | |
| height, border, image_size, data); | |
| } | |
| } else { | |
| api()->glCompressedTexImage3DFn(target, level, internal_format, width, | |
| height, depth, border, image_size, data); | |
| } | |
| if (reset_base_level) { | |
| api()->glTexParameteriFn(texture->target(), GL_TEXTURE_BASE_LEVEL, | |
| texture->base_level()); | |
| } | |
| } | |
| GLenum error = LOCAL_PEEK_GL_ERROR(func_name); | |
| if (error == GL_NO_ERROR) { | |
| texture_manager()->SetLevelInfo(texture_ref, target, level, internal_format, | |
| width, height, depth, border, 0, 0, | |
| gfx::Rect(width, height)); | |
| } | |
| // This may be a slow command. Exit command processing to allow for | |
| // context preemption and GPU watchdog checks. | |
| ExitCommandProcessingEarly(); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleTexImage2D(uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const char* func_name = "glTexImage2D"; | |
| const volatile gles2::cmds::TexImage2D& c = | |
| *static_cast<const volatile gles2::cmds::TexImage2D*>(cmd_data); | |
| TRACE_EVENT2("gpu", "GLES2DecoderImpl::HandleTexImage2D", | |
| "width", c.width, "height", c.height); | |
| // Set as failed for now, but if it successed, this will be set to not failed. | |
| texture_state_.tex_image_failed = true; | |
| GLenum target = static_cast<GLenum>(c.target); | |
| if (!validators_->texture_target.IsValid(target)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM(func_name, target, "target"); | |
| return error::kNoError; | |
| } | |
| TextureRef* texture_ref = | |
| texture_manager()->GetTextureInfoForTarget(&state_, target); | |
| ScopedBufferReattacher reattacher(this, texture_ref); | |
| GLint level = static_cast<GLint>(c.level); | |
| GLenum internal_format = static_cast<GLenum>(c.internalformat); | |
| GLsizei width = static_cast<GLsizei>(c.width); | |
| GLsizei height = static_cast<GLsizei>(c.height); | |
| GLint border = static_cast<GLint>(c.border); | |
| GLenum format = static_cast<GLenum>(c.format); | |
| GLenum type = static_cast<GLenum>(c.type); | |
| uint32_t pixels_shm_id = static_cast<uint32_t>(c.pixels_shm_id); | |
| uint32_t pixels_shm_offset = static_cast<uint32_t>(c.pixels_shm_offset); | |
| if (width < 0 || height < 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name, "dimensions < 0"); | |
| return error::kNoError; | |
| } | |
| PixelStoreParams params; | |
| Buffer* buffer = state_.bound_pixel_unpack_buffer.get(); | |
| if (buffer) { | |
| if (pixels_shm_id) | |
| return error::kInvalidArguments; | |
| params = state_.GetUnpackParams(ContextState::k2D); | |
| } else { | |
| if (!pixels_shm_id && pixels_shm_offset) | |
| return error::kInvalidArguments; | |
| // When reading from client buffer, the command buffer client side took | |
| // the responsibility to take the pixels from the client buffer and | |
| // unpack them according to the full ES3 pack parameters as source, all | |
| // parameters for 0 (except for alignment) as destination mem for the | |
| // service side. | |
| params.alignment = state_.unpack_alignment; | |
| } | |
| uint32_t pixels_size; | |
| uint32_t skip_size; | |
| uint32_t padding; | |
| if (!GLES2Util::ComputeImageDataSizesES3(width, height, 1, | |
| format, type, | |
| params, | |
| &pixels_size, | |
| nullptr, | |
| nullptr, | |
| &skip_size, | |
| &padding)) { | |
| return error::kOutOfBounds; | |
| } | |
| DCHECK_EQ(0u, skip_size); | |
| const void* pixels; | |
| if (pixels_shm_id) { | |
| pixels = GetSharedMemoryAs<const void*>( | |
| pixels_shm_id, pixels_shm_offset, pixels_size); | |
| if (!pixels) | |
| return error::kOutOfBounds; | |
| } else { | |
| pixels = reinterpret_cast<const void*>(pixels_shm_offset); | |
| } | |
| TextureManager::DoTexImageArguments args = { | |
| target, | |
| level, | |
| internal_format, | |
| width, | |
| height, | |
| 1, | |
| border, | |
| format, | |
| type, | |
| pixels, | |
| pixels_size, | |
| padding, | |
| TextureManager::DoTexImageArguments::CommandType::kTexImage2D}; | |
| texture_manager()->ValidateAndDoTexImage( | |
| &texture_state_, &state_, error_state_.get(), &framebuffer_state_, | |
| func_name, args); | |
| // This may be a slow command. Exit command processing to allow for | |
| // context preemption and GPU watchdog checks. | |
| ExitCommandProcessingEarly(); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleTexImage3D(uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| if (!feature_info_->IsWebGL2OrES3Context()) | |
| return error::kUnknownCommand; | |
| const char* func_name = "glTexImage3D"; | |
| const volatile gles2::cmds::TexImage3D& c = | |
| *static_cast<const volatile gles2::cmds::TexImage3D*>(cmd_data); | |
| TRACE_EVENT2("gpu", "GLES2DecoderImpl::HandleTexImage3D", | |
| "widthXheight", c.width * c.height, "depth", c.depth); | |
| // Set as failed for now, but if it successed, this will be set to not failed. | |
| texture_state_.tex_image_failed = true; | |
| GLenum target = static_cast<GLenum>(c.target); | |
| if (!validators_->texture_3_d_target.IsValid(target)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM(func_name, target, "target"); | |
| return error::kNoError; | |
| } | |
| TextureRef* texture_ref = | |
| texture_manager()->GetTextureInfoForTarget(&state_, target); | |
| ScopedBufferReattacher reattacher(this, texture_ref); | |
| GLint level = static_cast<GLint>(c.level); | |
| GLenum internal_format = static_cast<GLenum>(c.internalformat); | |
| GLsizei width = static_cast<GLsizei>(c.width); | |
| GLsizei height = static_cast<GLsizei>(c.height); | |
| GLsizei depth = static_cast<GLsizei>(c.depth); | |
| GLint border = static_cast<GLint>(c.border); | |
| GLenum format = static_cast<GLenum>(c.format); | |
| GLenum type = static_cast<GLenum>(c.type); | |
| uint32_t pixels_shm_id = static_cast<uint32_t>(c.pixels_shm_id); | |
| uint32_t pixels_shm_offset = static_cast<uint32_t>(c.pixels_shm_offset); | |
| if (width < 0 || height < 0 || depth < 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name, "dimensions < 0"); | |
| return error::kNoError; | |
| } | |
| if (target == GL_TEXTURE_3D && | |
| workarounds().lose_gl_context_when_increase_texture_3d_depth && | |
| texture_ref && texture_ref->texture()) { | |
| GLsizei current_weight = 0, current_height = 0, current_depth = 0; | |
| if (texture_ref->texture()->GetLevelSize(target, level, ¤t_weight, | |
| ¤t_height, ¤t_depth) && | |
| depth > current_depth) { | |
| MarkContextLost(error::kUnknown); | |
| group_->LoseContexts(error::kUnknown); | |
| return error::kLostContext; | |
| } | |
| } | |
| PixelStoreParams params; | |
| Buffer* buffer = state_.bound_pixel_unpack_buffer.get(); | |
| if (buffer) { | |
| if (pixels_shm_id) | |
| return error::kInvalidArguments; | |
| params = state_.GetUnpackParams(ContextState::k3D); | |
| } else { | |
| if (!pixels_shm_id && pixels_shm_offset) | |
| return error::kInvalidArguments; | |
| // When reading from client buffer, the command buffer client side took | |
| // the responsibility to take the pixels from the client buffer and | |
| // unpack them according to the full ES3 pack parameters as source, all | |
| // parameters for 0 (except for alignment) as destination mem for the | |
| // service side. | |
| params.alignment = state_.unpack_alignment; | |
| } | |
| uint32_t pixels_size; | |
| uint32_t skip_size; | |
| uint32_t padding; | |
| if (!GLES2Util::ComputeImageDataSizesES3(width, height, depth, | |
| format, type, | |
| params, | |
| &pixels_size, | |
| nullptr, | |
| nullptr, | |
| &skip_size, | |
| &padding)) { | |
| return error::kOutOfBounds; | |
| } | |
| DCHECK_EQ(0u, skip_size); | |
| const void* pixels; | |
| if (pixels_shm_id) { | |
| pixels = GetSharedMemoryAs<const void*>( | |
| pixels_shm_id, pixels_shm_offset, pixels_size); | |
| if (!pixels) | |
| return error::kOutOfBounds; | |
| } else { | |
| pixels = reinterpret_cast<const void*>(pixels_shm_offset); | |
| } | |
| TextureManager::DoTexImageArguments args = { | |
| target, | |
| level, | |
| internal_format, | |
| width, | |
| height, | |
| depth, | |
| border, | |
| format, | |
| type, | |
| pixels, | |
| pixels_size, | |
| padding, | |
| TextureManager::DoTexImageArguments::CommandType::kTexImage3D}; | |
| texture_manager()->ValidateAndDoTexImage( | |
| &texture_state_, &state_, error_state_.get(), &framebuffer_state_, | |
| func_name, args); | |
| // This may be a slow command. Exit command processing to allow for | |
| // context preemption and GPU watchdog checks. | |
| ExitCommandProcessingEarly(); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleCompressedTexSubImage2DBucket( | |
| uint32_t immediate_data_size, const volatile void* cmd_data) { | |
| const volatile gles2::cmds::CompressedTexSubImage2DBucket& c = | |
| *static_cast<const volatile gles2::cmds::CompressedTexSubImage2DBucket*>( | |
| cmd_data); | |
| GLenum target = static_cast<GLenum>(c.target); | |
| GLint level = static_cast<GLint>(c.level); | |
| GLint xoffset = static_cast<GLint>(c.xoffset); | |
| GLint yoffset = static_cast<GLint>(c.yoffset); | |
| GLsizei width = static_cast<GLsizei>(c.width); | |
| GLsizei height = static_cast<GLsizei>(c.height); | |
| GLenum format = static_cast<GLenum>(c.format); | |
| GLuint bucket_id = static_cast<GLuint>(c.bucket_id); | |
| if (state_.bound_pixel_unpack_buffer.get()) { | |
| return error::kInvalidArguments; | |
| } | |
| Bucket* bucket = GetBucket(bucket_id); | |
| if (!bucket) | |
| return error::kInvalidArguments; | |
| uint32_t image_size = bucket->size(); | |
| base::span<uint8_t> data = bucket->GetDataAsByteSpan(0, image_size); | |
| DCHECK_EQ(data.size(), image_size); | |
| return DoCompressedTexSubImage(target, level, xoffset, yoffset, 0, width, | |
| height, 1, format, image_size, data.data(), | |
| ContextState::k2D); | |
| } | |
| error::Error GLES2DecoderImpl::HandleCompressedTexSubImage2D( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::CompressedTexSubImage2D& c = | |
| *static_cast<const volatile gles2::cmds::CompressedTexSubImage2D*>( | |
| cmd_data); | |
| GLenum target = static_cast<GLenum>(c.target); | |
| GLint level = static_cast<GLint>(c.level); | |
| GLint xoffset = static_cast<GLint>(c.xoffset); | |
| GLint yoffset = static_cast<GLint>(c.yoffset); | |
| GLsizei width = static_cast<GLsizei>(c.width); | |
| GLsizei height = static_cast<GLsizei>(c.height); | |
| GLenum format = static_cast<GLenum>(c.format); | |
| GLsizei image_size = static_cast<GLsizei>(c.imageSize); | |
| uint32_t data_shm_id = c.data_shm_id; | |
| uint32_t data_shm_offset = c.data_shm_offset; | |
| const void* data; | |
| if (state_.bound_pixel_unpack_buffer.get()) { | |
| if (data_shm_id) { | |
| return error::kInvalidArguments; | |
| } | |
| data = reinterpret_cast<const void*>(data_shm_offset); | |
| } else { | |
| if (!data_shm_id) { | |
| return error::kInvalidArguments; | |
| } | |
| data = GetSharedMemoryAs<const void*>( | |
| data_shm_id, data_shm_offset, image_size); | |
| } | |
| return DoCompressedTexSubImage(target, level, xoffset, yoffset, 0, | |
| width, height, 1, format, image_size, data, | |
| ContextState::k2D); | |
| } | |
| error::Error GLES2DecoderImpl::DoCompressedTexSubImage( | |
| GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, | |
| GLsizei width, GLsizei height, GLsizei depth, GLenum format, | |
| GLsizei image_size, const void * data, ContextState::Dimension dimension) { | |
| const char* func_name; | |
| if (dimension == ContextState::k2D) { | |
| func_name = "glCompressedTexSubImage2D"; | |
| if (!validators_->texture_target.IsValid(target)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM(func_name, target, "target"); | |
| return error::kNoError; | |
| } | |
| } else { | |
| DCHECK_EQ(ContextState::k3D, dimension); | |
| func_name = "glCompressedTexSubImage3D"; | |
| if (!validators_->texture_3_d_target.IsValid(target)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM(func_name, target, "target"); | |
| return error::kNoError; | |
| } | |
| } | |
| if (!validators_->compressed_texture_format.IsValid(format)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM(func_name, format, "format"); | |
| return error::kNoError; | |
| } | |
| if (image_size < 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name, "imageSize < 0"); | |
| return error::kNoError; | |
| } | |
| TextureRef* texture_ref = texture_manager()->GetTextureInfoForTarget( | |
| &state_, target); | |
| if (!texture_ref) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, func_name, "no texture bound at target"); | |
| return error::kNoError; | |
| } | |
| Texture* texture = texture_ref->texture(); | |
| if (!texture_manager()->ValidForTextureTarget(texture, level, width, height, | |
| depth)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name, "dimensions out of range"); | |
| return error::kNoError; | |
| } | |
| GLenum type = 0; | |
| GLenum internal_format = 0; | |
| if (!texture->GetLevelType(target, level, &type, &internal_format)) { | |
| std::string msg = base::StringPrintf("level %d does not exist", level); | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, msg.c_str()); | |
| return error::kNoError; | |
| } | |
| if (internal_format != format) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "format does not match internalformat."); | |
| return error::kNoError; | |
| } | |
| if (!texture->ValidForTexture(target, level, xoffset, yoffset, zoffset, | |
| width, height, depth)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name, "bad dimensions."); | |
| return error::kNoError; | |
| } | |
| if (!ValidateCompressedTexFuncData( | |
| func_name, width, height, depth, format, image_size, data) || | |
| !ValidateCompressedTexSubDimensions( | |
| func_name, target, level, xoffset, yoffset, zoffset, | |
| width, height, depth, format, texture)) { | |
| return error::kNoError; | |
| } | |
| if (image_size > 0 && !state_.bound_pixel_unpack_buffer && !data) | |
| return error::kInvalidArguments; | |
| bool should_set_level_cleared = false; | |
| if (!texture->IsLevelCleared(target, level)) { | |
| // This can only happen if the compressed texture was allocated | |
| // using TexStorage{2|3}D. | |
| DCHECK(texture->IsImmutable()); | |
| GLsizei level_width = 0, level_height = 0, level_depth = 0; | |
| bool success = texture->GetLevelSize( | |
| target, level, &level_width, &level_height, &level_depth); | |
| DCHECK(success); | |
| if (xoffset == 0 && width == level_width && | |
| yoffset == 0 && height == level_height && | |
| zoffset == 0 && depth == level_depth) { | |
| // We can skip the clear if we're uploading the entire level. | |
| should_set_level_cleared = true; | |
| } else { | |
| texture_manager()->ClearTextureLevel(this, texture_ref, target, level); | |
| DCHECK(texture->IsLevelCleared(target, level)); | |
| } | |
| } | |
| if (should_set_level_cleared) { | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER(func_name); | |
| } | |
| const CompressedFormatInfo* format_info = | |
| GetCompressedFormatInfo(internal_format); | |
| if (format_info != nullptr && !format_info->support_check(*feature_info_)) { | |
| auto decompressed_data = DecompressTextureData( | |
| state_, *format_info, width, height, depth, image_size, data); | |
| if (decompressed_data.empty()) { | |
| MarkContextLost(error::kGuilty); | |
| group_->LoseContexts(error::kInnocent); | |
| return error::kLostContext; | |
| } | |
| ScopedPixelUnpackState reset_restore(&state_); | |
| if (dimension == ContextState::k2D) { | |
| api()->glTexSubImage2DFn(target, level, xoffset, yoffset, width, height, | |
| format_info->decompressed_format, | |
| format_info->decompressed_type, | |
| decompressed_data.data()); | |
| } else { | |
| api()->glTexSubImage3DFn(target, level, xoffset, yoffset, zoffset, width, | |
| height, depth, format_info->decompressed_format, | |
| format_info->decompressed_type, | |
| decompressed_data.data()); | |
| } | |
| } else { | |
| // Compressed uploads must ignore client unpack state; see the comment in | |
| // DoCompressedTexImage. | |
| ScopedCompressedUnpackStateScrub scrub(&state_); | |
| bool reset_base_level = workarounds().reset_base_level_for_astc_image && | |
| IsASTCFormat(format) && texture->base_level() != 0; | |
| if (reset_base_level) { | |
| api()->glTexParameteriFn(texture->target(), GL_TEXTURE_BASE_LEVEL, 0); | |
| } | |
| if (dimension == ContextState::k2D) { | |
| api()->glCompressedTexSubImage2DFn(target, level, xoffset, yoffset, width, | |
| height, format, image_size, data); | |
| } else { | |
| api()->glCompressedTexSubImage3DFn(target, level, xoffset, yoffset, | |
| zoffset, width, height, depth, format, | |
| image_size, data); | |
| } | |
| if (reset_base_level) { | |
| api()->glTexParameteriFn(texture->target(), GL_TEXTURE_BASE_LEVEL, | |
| texture->base_level()); | |
| } | |
| } | |
| if (should_set_level_cleared) { | |
| if (LOCAL_PEEK_GL_ERROR(func_name) == GL_NO_ERROR) { | |
| texture_manager()->SetLevelCleared(texture_ref, target, level, true); | |
| } | |
| } | |
| // This may be a slow command. Exit command processing to allow for | |
| // context preemption and GPU watchdog checks. | |
| ExitCommandProcessingEarly(); | |
| return error::kNoError; | |
| } | |
| bool GLES2DecoderImpl::ValidateCopyTexFormat(const char* func_name, | |
| GLenum internal_format, | |
| GLenum read_format, | |
| GLenum read_type) { | |
| std::string output_error_msg; | |
| if (!ValidateCopyTexFormatHelper(GetFeatureInfo(), internal_format, | |
| read_format, read_type, &output_error_msg)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| output_error_msg.c_str()); | |
| return false; | |
| } | |
| return true; | |
| } | |
| void GLES2DecoderImpl::DoCopyTexImage2D( | |
| GLenum target, | |
| GLint level, | |
| GLenum internal_format, | |
| GLint x, | |
| GLint y, | |
| GLsizei width, | |
| GLsizei height, | |
| GLint border) { | |
| const char* func_name = "glCopyTexImage2D"; | |
| TextureRef* texture_ref = texture_manager()->GetTextureInfoForTarget( | |
| &state_, target); | |
| if (!texture_ref) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, func_name, "unknown texture for target"); | |
| return; | |
| } | |
| ScopedBufferReattacher reattacher(this, texture_ref); | |
| Texture* texture = texture_ref->texture(); | |
| if (texture->IsImmutable()) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, func_name, "texture is immutable"); | |
| return; | |
| } | |
| if (!texture_manager()->ValidForTextureTarget(texture, level, width, height, | |
| 1) || | |
| border != 0) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, func_name, "dimensions out of range"); | |
| return; | |
| } | |
| if (!CheckBoundReadFramebufferValid(func_name, | |
| GL_INVALID_FRAMEBUFFER_OPERATION)) { | |
| return; | |
| } | |
| if (IsBoundReadFramebufferMultisampledRenderbuffer()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "cannot copy from a multisampled framebuffer"); | |
| return; | |
| } | |
| GLenum read_format = GetBoundReadFramebufferInternalFormat(); | |
| GLenum read_type = GetBoundReadFramebufferTextureType(); | |
| if (!ValidateCopyTexFormat(func_name, internal_format, | |
| read_format, read_type)) { | |
| return; | |
| } | |
| uint32_t pixels_size = 0; | |
| // TODO(piman): OpenGL ES 3.0.4 Section 3.8.5 specifies how to pick an | |
| // effective internal format if internal_format is unsized, which is a fairly | |
| // involved logic. For now, just make sure we pick something valid. | |
| GLenum format = | |
| TextureManager::ExtractFormatFromStorageFormat(internal_format); | |
| GLenum type = TextureManager::ExtractTypeFromStorageFormat(internal_format); | |
| bool internal_format_unsized = internal_format == format; | |
| // The picks made by the temporary logic above may not be valid on ES3. | |
| // This if-block checks the temporary logic and should be removed with it. | |
| if (internal_format_unsized && feature_info_->IsWebGL2OrES3Context()) { | |
| // While there are other possible types for unsized formats (cf. OpenGL ES | |
| // 3.0.5, section 3.7, table 3.3, page 113), they won't appear here. | |
| // ExtractTypeFromStorageFormat will always return UNSIGNED_BYTE for | |
| // unsized formats. | |
| DCHECK(type == GL_UNSIGNED_BYTE); | |
| // Changing the internal format here is probably not completely | |
| // correct. This is the "effective" internal format, and the spec | |
| // says "effective internal format is used by the GL for purposes | |
| // such as texture completeness or type checks for CopyTex* | |
| // commands". But we don't have a separate concept of "effective" | |
| // vs. "actual" internal format, so this will have to do for now. See | |
| // Table 3.12 and associated explanatory text in the OpenGL ES 3.0.6 | |
| // spec for more information. | |
| bool attempt_sized_upgrade = | |
| read_format == GL_RGB || read_format == GL_RGB8 || | |
| read_format == GL_RGBA || read_format == GL_RGBA8; | |
| switch (internal_format) { | |
| case GL_RGB: | |
| if (attempt_sized_upgrade) { | |
| internal_format = GL_RGB8; | |
| } | |
| break; | |
| case GL_RGBA: | |
| if (attempt_sized_upgrade) { | |
| internal_format = GL_RGBA8; | |
| } | |
| break; | |
| case GL_LUMINANCE_ALPHA: | |
| case GL_LUMINANCE: | |
| case GL_ALPHA: | |
| case GL_BGRA_EXT: | |
| // There are no GL constants for sized versions of these internal | |
| // formats. We'll just go ahead with the unsized ones. | |
| break; | |
| default: | |
| // Other unsized internal_formats are invalid in ES3. | |
| format = GL_NONE; | |
| break; | |
| } | |
| } | |
| if (!format || !type) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, | |
| func_name, "Invalid unsized internal format."); | |
| return; | |
| } | |
| DCHECK(texture_manager()->ValidateTextureParameters( | |
| error_state_.get(), func_name, true, format, type, internal_format, | |
| level)); | |
| // Only target image size is validated here. | |
| if (!GLES2Util::ComputeImageDataSizes(width, height, 1, format, type, | |
| state_.unpack_alignment, &pixels_size, | |
| nullptr, nullptr)) { | |
| LOCAL_SET_GL_ERROR(GL_OUT_OF_MEMORY, func_name, "dimensions too large"); | |
| return; | |
| } | |
| if (FormsTextureCopyingFeedbackLoop(texture_ref, level, 0)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, | |
| func_name, "source and destination textures are the same"); | |
| return; | |
| } | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER(func_name); | |
| gfx::Size size = GetBoundReadFramebufferSize(); | |
| if (texture->IsAttachedToFramebuffer()) { | |
| framebuffer_state_.clear_state_dirty = true; | |
| } | |
| bool requires_luma_blit = | |
| CopyTexImageResourceManager::CopyTexImageRequiresBlit(feature_info_.get(), | |
| format); | |
| if (requires_luma_blit && | |
| !InitializeCopyTexImageBlitter(func_name)) { | |
| return; | |
| } | |
| // If fbo's read buffer and the target texture are the same texture, but | |
| // different levels, and if the read buffer is non-base texture level, | |
| // then following internal glTexImage2D() calls may change the target texture | |
| // and make the originally mipmap complete texture mipmap incomplete, which | |
| // in turn make the fbo incomplete. | |
| // In order to avoid that, we clamp the BASE_LEVEL and MAX_LEVEL to the same | |
| // texture level that's attached to the fbo's read buffer. | |
| bool reset_source_texture_base_level_max_level = false; | |
| GLint attached_texture_level = -1; | |
| Framebuffer* framebuffer = GetBoundReadFramebuffer(); | |
| if (framebuffer) { | |
| const Framebuffer::Attachment* attachment = | |
| framebuffer->GetReadBufferAttachment(); | |
| if (attachment->IsTexture(texture_ref)) { | |
| DCHECK(attachment->IsTextureAttachment()); | |
| attached_texture_level = attachment->level(); | |
| DCHECK_GE(attached_texture_level, 0); | |
| if (attached_texture_level != texture->base_level()) | |
| reset_source_texture_base_level_max_level = true; | |
| } | |
| } | |
| if (reset_source_texture_base_level_max_level) { | |
| api()->glTexParameteriFn(texture->target(), GL_TEXTURE_BASE_LEVEL, | |
| attached_texture_level); | |
| api()->glTexParameteriFn(texture->target(), GL_TEXTURE_MAX_LEVEL, | |
| attached_texture_level); | |
| } | |
| // Clip to size to source dimensions | |
| gfx::Rect src(x, y, width, height); | |
| const gfx::Rect dst(0, 0, size.width(), size.height()); | |
| src.Intersect(dst); | |
| GLenum final_internal_format = TextureManager::AdjustTexInternalFormat( | |
| feature_info_.get(), internal_format, type); | |
| if (workarounds().force_int_or_srgb_cube_texture_complete && | |
| texture->target() == GL_TEXTURE_CUBE_MAP && | |
| (GLES2Util::IsIntegerFormat(final_internal_format) || | |
| GLES2Util::GetColorEncodingFromInternalFormat(final_internal_format) == | |
| GL_SRGB)) { | |
| TextureManager::DoTexImageArguments args = { | |
| target, | |
| level, | |
| final_internal_format, | |
| width, | |
| height, | |
| 1, | |
| border, | |
| format, | |
| type, | |
| nullptr, | |
| pixels_size, | |
| 0, | |
| TextureManager::DoTexImageArguments::CommandType::kTexImage2D}; | |
| texture_manager()->WorkaroundCopyTexImageCubeMap( | |
| &texture_state_, &state_, error_state_.get(), &framebuffer_state_, | |
| texture_ref, func_name, args); | |
| } | |
| if (src.x() != x || src.y() != y || | |
| src.width() != width || src.height() != height || | |
| final_internal_format == GL_BGRA_EXT) { | |
| // GL_BGRA_EXT is not allowed as internalformat for glCopyTexImage2D, | |
| // which is a bit of a quirk in the spec, but this path works. | |
| { | |
| // Add extra scope to destroy zero and the object it owns right | |
| // after its usage. | |
| // some part was clipped so clear the rect. | |
| auto zero = base::HeapArray<char>::WithSize(pixels_size); | |
| ScopedPixelUnpackState reset_restore(&state_); | |
| api()->glTexImage2DFn(target, level, final_internal_format, width, height, | |
| border, format, type, zero.data()); | |
| } | |
| if (!src.IsEmpty()) { | |
| GLint destX = src.x() - x; | |
| GLint destY = src.y() - y; | |
| if (requires_luma_blit) { | |
| copy_tex_image_blit_->DoCopyTexSubImageToLUMACompatibilityTexture( | |
| this, texture->service_id(), texture->target(), target, format, | |
| type, level, destX, destY, 0, | |
| src.x(), src.y(), src.width(), src.height(), | |
| GetBoundReadFramebufferServiceId(), | |
| GetBoundReadFramebufferInternalFormat()); | |
| } else { | |
| api()->glCopyTexSubImage2DFn(target, level, destX, destY, src.x(), | |
| src.y(), src.width(), src.height()); | |
| } | |
| } | |
| } else { | |
| if (requires_luma_blit) { | |
| copy_tex_image_blit_->DoCopyTexImage2DToLUMACompatibilityTexture( | |
| this, texture->service_id(), texture->target(), target, format, type, | |
| level, internal_format, x, y, width, height, | |
| GetBoundReadFramebufferServiceId(), | |
| GetBoundReadFramebufferInternalFormat()); | |
| } else { | |
| api()->glCopyTexImage2DFn(target, level, final_internal_format, x, y, | |
| width, height, border); | |
| } | |
| } | |
| if (reset_source_texture_base_level_max_level) { | |
| api()->glTexParameteriFn(texture->target(), GL_TEXTURE_BASE_LEVEL, | |
| texture->base_level()); | |
| api()->glTexParameteriFn(texture->target(), GL_TEXTURE_MAX_LEVEL, | |
| texture->max_level()); | |
| } | |
| GLenum error = LOCAL_PEEK_GL_ERROR(func_name); | |
| if (error == GL_NO_ERROR) { | |
| texture_manager()->SetLevelInfo(texture_ref, target, level, internal_format, | |
| width, height, 1, border, format, | |
| type, gfx::Rect(width, height)); | |
| texture->ApplyFormatWorkarounds(feature_info_.get()); | |
| } | |
| // This may be a slow command. Exit command processing to allow for | |
| // context preemption and GPU watchdog checks. | |
| ExitCommandProcessingEarly(); | |
| } | |
| void GLES2DecoderImpl::DoCopyTexSubImage2D( | |
| GLenum target, | |
| GLint level, | |
| GLint xoffset, | |
| GLint yoffset, | |
| GLint x, | |
| GLint y, | |
| GLsizei width, | |
| GLsizei height) { | |
| const char* func_name = "glCopyTexSubImage2D"; | |
| TextureRef* texture_ref = texture_manager()->GetTextureInfoForTarget( | |
| &state_, target); | |
| if (!texture_ref) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, func_name, "unknown texture for target"); | |
| return; | |
| } | |
| Texture* texture = texture_ref->texture(); | |
| GLenum type = 0; | |
| GLenum internal_format = 0; | |
| if (!texture->GetLevelType(target, level, &type, &internal_format) || | |
| !texture->ValidForTexture( | |
| target, level, xoffset, yoffset, 0, width, height, 1)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name, "bad dimensions."); | |
| return; | |
| } | |
| if (!CheckBoundReadFramebufferValid(func_name, | |
| GL_INVALID_FRAMEBUFFER_OPERATION)) { | |
| return; | |
| } | |
| if (IsBoundReadFramebufferMultisampledRenderbuffer()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "cannot copy from a multisampled framebuffer"); | |
| return; | |
| } | |
| GLenum read_format = GetBoundReadFramebufferInternalFormat(); | |
| GLenum read_type = GetBoundReadFramebufferTextureType(); | |
| if (!ValidateCopyTexFormat(func_name, internal_format, | |
| read_format, read_type)) { | |
| return; | |
| } | |
| if (FormsTextureCopyingFeedbackLoop(texture_ref, level, 0)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, | |
| func_name, "source and destination textures are the same"); | |
| return; | |
| } | |
| gfx::Size size = GetBoundReadFramebufferSize(); | |
| gfx::Rect src(x, y, width, height); | |
| const gfx::Rect dst(0, 0, size.width(), size.height()); | |
| src.Intersect(dst); | |
| if (src.IsEmpty()) | |
| return; | |
| GLint dx = src.x() - x; | |
| GLint dy = src.y() - y; | |
| GLint destX = xoffset + dx; | |
| GLint destY = yoffset + dy; | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER(func_name); | |
| // It's only legal to skip clearing the level of the target texture | |
| // if the entire level is being redefined. | |
| GLsizei level_width = 0; | |
| GLsizei level_height = 0; | |
| GLsizei level_depth = 0; | |
| bool have_level = texture->GetLevelSize( | |
| target, level, &level_width, &level_height, &level_depth); | |
| // Validated above. | |
| DCHECK(have_level); | |
| bool set_cleared = false; | |
| gfx::Rect cleared_rect_to_set; | |
| bool set_cleared_rect = false; | |
| if (destX == 0 && destY == 0 && | |
| src.width() == level_width && src.height() == level_height) { | |
| // Write all pixels in below. | |
| set_cleared = true; | |
| } else { | |
| gfx::Rect cleared_rect; | |
| if (TextureManager::CombineAdjacentRects( | |
| texture->GetLevelClearedRect(target, level), | |
| gfx::Rect(destX, destY, src.width(), src.height()), | |
| &cleared_rect)) { | |
| DCHECK_GE(cleared_rect.size().GetArea(), | |
| texture->GetLevelClearedRect(target, level).size().GetArea()); | |
| cleared_rect_to_set = cleared_rect; | |
| set_cleared_rect = true; | |
| } else { | |
| // Otherwise clear part of texture level that is not already cleared. | |
| if (!texture_manager()->ClearTextureLevel(this, texture_ref, target, | |
| level)) { | |
| LOCAL_SET_GL_ERROR(GL_OUT_OF_MEMORY, func_name, "dimensions too big"); | |
| return; | |
| } | |
| } | |
| } | |
| if (CopyTexImageResourceManager::CopyTexImageRequiresBlit( | |
| feature_info_.get(), internal_format)) { | |
| if (!InitializeCopyTexImageBlitter("glCopyTexSubImage2D")) { | |
| return; | |
| } | |
| copy_tex_image_blit_->DoCopyTexSubImageToLUMACompatibilityTexture( | |
| this, texture->service_id(), texture->target(), target, | |
| internal_format, type, level, destX, destY, 0, | |
| src.x(), src.y(), src.width(), src.height(), | |
| GetBoundReadFramebufferServiceId(), | |
| GetBoundReadFramebufferInternalFormat()); | |
| } else { | |
| api()->glCopyTexSubImage2DFn(target, level, destX, destY, src.x(), src.y(), | |
| src.width(), src.height()); | |
| } | |
| if (LOCAL_PEEK_GL_ERROR(func_name) == GL_NO_ERROR) { | |
| if (set_cleared) { | |
| texture_manager()->SetLevelCleared(texture_ref, target, level, true); | |
| } else if (set_cleared_rect) { | |
| texture_manager()->SetLevelClearedRect(texture_ref, target, level, | |
| cleared_rect_to_set); | |
| } | |
| } | |
| // This may be a slow command. Exit command processing to allow for | |
| // context preemption and GPU watchdog checks. | |
| ExitCommandProcessingEarly(); | |
| } | |
| void GLES2DecoderImpl::DoCopyTexSubImage3D( | |
| GLenum target, | |
| GLint level, | |
| GLint xoffset, | |
| GLint yoffset, | |
| GLint zoffset, | |
| GLint x, | |
| GLint y, | |
| GLsizei width, | |
| GLsizei height) { | |
| const char* func_name = "glCopyTexSubImage3D"; | |
| TextureRef* texture_ref = texture_manager()->GetTextureInfoForTarget( | |
| &state_, target); | |
| if (!texture_ref) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, func_name, "unknown texture for target"); | |
| return; | |
| } | |
| Texture* texture = texture_ref->texture(); | |
| GLenum type = 0; | |
| GLenum internal_format = 0; | |
| if (!texture->GetLevelType(target, level, &type, &internal_format) || | |
| !texture->ValidForTexture( | |
| target, level, xoffset, yoffset, zoffset, width, height, 1)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name, "bad dimensions."); | |
| return; | |
| } | |
| if (!CheckBoundReadFramebufferValid(func_name, | |
| GL_INVALID_FRAMEBUFFER_OPERATION)) { | |
| return; | |
| } | |
| if (IsBoundReadFramebufferMultisampledRenderbuffer()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "cannot copy from a multisampled framebuffer"); | |
| return; | |
| } | |
| GLenum read_format = GetBoundReadFramebufferInternalFormat(); | |
| GLenum read_type = GetBoundReadFramebufferTextureType(); | |
| if (!ValidateCopyTexFormat(func_name, internal_format, | |
| read_format, read_type)) { | |
| return; | |
| } | |
| if (FormsTextureCopyingFeedbackLoop(texture_ref, level, zoffset)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, | |
| func_name, "source and destination textures are the same"); | |
| return; | |
| } | |
| gfx::Size size = GetBoundReadFramebufferSize(); | |
| gfx::Rect src(x, y, width, height); | |
| const gfx::Rect dst(0, 0, size.width(), size.height()); | |
| src.Intersect(dst); | |
| if (src.IsEmpty()) | |
| return; | |
| GLint dx = src.x() - x; | |
| GLint dy = src.y() - y; | |
| GLint destX = xoffset + dx; | |
| GLint destY = yoffset + dy; | |
| // For 3D textures, we always clear the entire texture to 0 if it is not | |
| // cleared. See the code in TextureManager::ValidateAndDoTexSubImage | |
| // for TexSubImage3D. | |
| if (!texture->IsLevelCleared(target, level)) { | |
| if (!texture_manager()->ClearTextureLevel(this, texture_ref, target, | |
| level)) { | |
| LOCAL_SET_GL_ERROR(GL_OUT_OF_MEMORY, func_name, "dimensions too big"); | |
| return; | |
| } | |
| DCHECK(texture->IsLevelCleared(target, level)); | |
| } | |
| if (CopyTexImageResourceManager::CopyTexImageRequiresBlit( | |
| feature_info_.get(), internal_format)) { | |
| if (!InitializeCopyTexImageBlitter(func_name)) { | |
| return; | |
| } | |
| copy_tex_image_blit_->DoCopyTexSubImageToLUMACompatibilityTexture( | |
| this, texture->service_id(), texture->target(), target, | |
| internal_format, type, level, destX, destY, zoffset, | |
| src.x(), src.y(), src.width(), src.height(), | |
| GetBoundReadFramebufferServiceId(), | |
| GetBoundReadFramebufferInternalFormat()); | |
| } else { | |
| api()->glCopyTexSubImage3DFn(target, level, destX, destY, zoffset, src.x(), | |
| src.y(), src.width(), src.height()); | |
| } | |
| // This may be a slow command. Exit command processing to allow for | |
| // context preemption and GPU watchdog checks. | |
| ExitCommandProcessingEarly(); | |
| } | |
| error::Error GLES2DecoderImpl::HandleTexSubImage2D( | |
| uint32_t immediate_data_size, const volatile void* cmd_data) { | |
| const char* func_name = "glTexSubImage2D"; | |
| const volatile gles2::cmds::TexSubImage2D& c = | |
| *static_cast<const volatile gles2::cmds::TexSubImage2D*>(cmd_data); | |
| TRACE_EVENT2("gpu", "GLES2DecoderImpl::HandleTexSubImage2D", | |
| "width", c.width, "height", c.height); | |
| GLboolean internal = static_cast<GLboolean>(c.internal); | |
| if (internal == GL_TRUE && texture_state_.tex_image_failed) | |
| return error::kNoError; | |
| GLenum target = static_cast<GLenum>(c.target); | |
| GLint level = static_cast<GLint>(c.level); | |
| GLint xoffset = static_cast<GLint>(c.xoffset); | |
| GLint yoffset = static_cast<GLint>(c.yoffset); | |
| GLsizei width = static_cast<GLsizei>(c.width); | |
| GLsizei height = static_cast<GLsizei>(c.height); | |
| GLenum format = static_cast<GLenum>(c.format); | |
| GLenum type = static_cast<GLenum>(c.type); | |
| uint32_t pixels_shm_id = static_cast<uint32_t>(c.pixels_shm_id); | |
| uint32_t pixels_shm_offset = static_cast<uint32_t>(c.pixels_shm_offset); | |
| if (width < 0 || height < 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name, "dimensions < 0"); | |
| return error::kNoError; | |
| } | |
| PixelStoreParams params; | |
| Buffer* buffer = state_.bound_pixel_unpack_buffer.get(); | |
| if (buffer) { | |
| if (pixels_shm_id) | |
| return error::kInvalidArguments; | |
| params = state_.GetUnpackParams(ContextState::k2D); | |
| } else { | |
| if (!pixels_shm_id && pixels_shm_offset) | |
| return error::kInvalidArguments; | |
| // When reading from client buffer, the command buffer client side took | |
| // the responsibility to take the pixels from the client buffer and | |
| // unpack them according to the full ES3 pack parameters as source, all | |
| // parameters for 0 (except for alignment) as destination mem for the | |
| // service side. | |
| params.alignment = state_.unpack_alignment; | |
| } | |
| uint32_t pixels_size; | |
| uint32_t skip_size; | |
| uint32_t padding; | |
| if (!GLES2Util::ComputeImageDataSizesES3(width, height, 1, | |
| format, type, | |
| params, | |
| &pixels_size, | |
| nullptr, | |
| nullptr, | |
| &skip_size, | |
| &padding)) { | |
| return error::kOutOfBounds; | |
| } | |
| DCHECK_EQ(0u, skip_size); | |
| const void* pixels; | |
| if (pixels_shm_id) { | |
| pixels = GetSharedMemoryAs<const void*>( | |
| pixels_shm_id, pixels_shm_offset, pixels_size); | |
| if (!pixels) | |
| return error::kOutOfBounds; | |
| } else { | |
| DCHECK(buffer || !pixels_shm_offset); | |
| pixels = reinterpret_cast<const void*>(pixels_shm_offset); | |
| } | |
| TextureManager::DoTexSubImageArguments args = { | |
| target, | |
| level, | |
| xoffset, | |
| yoffset, | |
| 0, | |
| width, | |
| height, | |
| 1, | |
| format, | |
| type, | |
| pixels, | |
| pixels_size, | |
| padding, | |
| TextureManager::DoTexSubImageArguments::CommandType::kTexSubImage2D}; | |
| texture_manager()->ValidateAndDoTexSubImage( | |
| this, &texture_state_, &state_, error_state_.get(), &framebuffer_state_, | |
| func_name, args); | |
| // This may be a slow command. Exit command processing to allow for | |
| // context preemption and GPU watchdog checks. | |
| ExitCommandProcessingEarly(); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleTexSubImage3D( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| if (!feature_info_->IsWebGL2OrES3Context()) | |
| return error::kUnknownCommand; | |
| const char* func_name = "glTexSubImage3D"; | |
| const volatile gles2::cmds::TexSubImage3D& c = | |
| *static_cast<const volatile gles2::cmds::TexSubImage3D*>(cmd_data); | |
| TRACE_EVENT2("gpu", "GLES2DecoderImpl::HandleTexSubImage3D", | |
| "widthXheight", c.width * c.height, "depth", c.depth); | |
| GLboolean internal = static_cast<GLboolean>(c.internal); | |
| if (internal == GL_TRUE && texture_state_.tex_image_failed) | |
| return error::kNoError; | |
| GLenum target = static_cast<GLenum>(c.target); | |
| GLint level = static_cast<GLint>(c.level); | |
| GLint xoffset = static_cast<GLint>(c.xoffset); | |
| GLint yoffset = static_cast<GLint>(c.yoffset); | |
| GLint zoffset = static_cast<GLint>(c.zoffset); | |
| GLsizei width = static_cast<GLsizei>(c.width); | |
| GLsizei height = static_cast<GLsizei>(c.height); | |
| GLsizei depth = static_cast<GLsizei>(c.depth); | |
| GLenum format = static_cast<GLenum>(c.format); | |
| GLenum type = static_cast<GLenum>(c.type); | |
| uint32_t pixels_shm_id = static_cast<uint32_t>(c.pixels_shm_id); | |
| uint32_t pixels_shm_offset = static_cast<uint32_t>(c.pixels_shm_offset); | |
| if (width < 0 || height < 0 || depth < 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name, "dimensions < 0"); | |
| return error::kNoError; | |
| } | |
| PixelStoreParams params; | |
| Buffer* buffer = state_.bound_pixel_unpack_buffer.get(); | |
| if (buffer) { | |
| if (pixels_shm_id) | |
| return error::kInvalidArguments; | |
| params = state_.GetUnpackParams(ContextState::k3D); | |
| } else { | |
| if (!pixels_shm_id && pixels_shm_offset) | |
| return error::kInvalidArguments; | |
| // When reading from client buffer, the command buffer client side took | |
| // the responsibility to take the pixels from the client buffer and | |
| // unpack them according to the full ES3 pack parameters as source, all | |
| // parameters for 0 (except for alignment) as destination mem for the | |
| // service side. | |
| params.alignment = state_.unpack_alignment; | |
| } | |
| uint32_t pixels_size; | |
| uint32_t skip_size; | |
| uint32_t padding; | |
| if (!GLES2Util::ComputeImageDataSizesES3(width, height, depth, | |
| format, type, | |
| params, | |
| &pixels_size, | |
| nullptr, | |
| nullptr, | |
| &skip_size, | |
| &padding)) { | |
| return error::kOutOfBounds; | |
| } | |
| DCHECK_EQ(0u, skip_size); | |
| const void* pixels; | |
| if (pixels_shm_id) { | |
| pixels = GetSharedMemoryAs<const void*>( | |
| pixels_shm_id, pixels_shm_offset, pixels_size); | |
| if (!pixels) | |
| return error::kOutOfBounds; | |
| } else { | |
| DCHECK(buffer || !pixels_shm_offset); | |
| pixels = reinterpret_cast<const void*>(pixels_shm_offset); | |
| } | |
| TextureManager::DoTexSubImageArguments args = { | |
| target, | |
| level, | |
| xoffset, | |
| yoffset, | |
| zoffset, | |
| width, | |
| height, | |
| depth, | |
| format, | |
| type, | |
| pixels, | |
| pixels_size, | |
| padding, | |
| TextureManager::DoTexSubImageArguments::CommandType::kTexSubImage3D}; | |
| texture_manager()->ValidateAndDoTexSubImage( | |
| this, &texture_state_, &state_, error_state_.get(), &framebuffer_state_, | |
| func_name, args); | |
| // This may be a slow command. Exit command processing to allow for | |
| // context preemption and GPU watchdog checks. | |
| ExitCommandProcessingEarly(); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetVertexAttribPointerv( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::GetVertexAttribPointerv& c = | |
| *static_cast<const volatile gles2::cmds::GetVertexAttribPointerv*>( | |
| cmd_data); | |
| GLuint index = static_cast<GLuint>(c.index); | |
| GLenum pname = static_cast<GLenum>(c.pname); | |
| typedef cmds::GetVertexAttribPointerv::Result Result; | |
| Result* result = | |
| GetSharedMemoryAs<Result*>(c.pointer_shm_id, c.pointer_shm_offset, | |
| Result::ComputeSize(1).ValueOrDie()); | |
| if (!result) { | |
| return error::kOutOfBounds; | |
| } | |
| // Check that the client initialized the result. | |
| if (result->size != 0) { | |
| return error::kInvalidArguments; | |
| } | |
| if (!validators_->vertex_pointer.IsValid(pname)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM( | |
| "glGetVertexAttribPointerv", pname, "pname"); | |
| return error::kNoError; | |
| } | |
| if (index >= group_->max_vertex_attribs()) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glGetVertexAttribPointerv", "index out of range."); | |
| return error::kNoError; | |
| } | |
| result->SetNumResults(1); | |
| *result->GetData() = | |
| state_.vertex_attrib_manager->GetVertexAttrib(index)->offset(); | |
| return error::kNoError; | |
| } | |
| template <class T> | |
| bool GLES2DecoderImpl::GetUniformSetup(GLuint program_id, | |
| GLint fake_location, | |
| uint32_t shm_id, | |
| uint32_t shm_offset, | |
| error::Error* error, | |
| GLint* real_location, | |
| GLuint* service_id, | |
| SizedResult<T>** result_pointer, | |
| GLenum* result_type, | |
| GLsizei* result_size) { | |
| DCHECK(error); | |
| DCHECK(service_id); | |
| DCHECK(result_pointer); | |
| DCHECK(result_type); | |
| DCHECK(result_size); | |
| DCHECK(real_location); | |
| *error = error::kNoError; | |
| // Make sure we have enough room for the result on failure. | |
| SizedResult<T>* result; | |
| result = GetSharedMemoryAs<SizedResult<T>*>( | |
| shm_id, shm_offset, SizedResult<T>::ComputeSize(0).ValueOrDie()); | |
| if (!result) { | |
| *error = error::kOutOfBounds; | |
| return false; | |
| } | |
| *result_pointer = result; | |
| // Set the result size to 0 so the client does not have to check for success. | |
| result->SetNumResults(0); | |
| Program* program = GetProgramInfoNotShader(program_id, "glGetUniform"); | |
| if (!program) { | |
| return false; | |
| } | |
| if (!program->IsValid()) { | |
| // Program was not linked successfully. (ie, glLinkProgram) | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, "glGetUniform", "program not linked"); | |
| return false; | |
| } | |
| *service_id = program->service_id(); | |
| GLint array_index = -1; | |
| const Program::UniformInfo* uniform_info = | |
| program->GetUniformInfoByFakeLocation( | |
| fake_location, real_location, &array_index); | |
| if (!uniform_info) { | |
| // No such location. | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, "glGetUniform", "unknown location"); | |
| return false; | |
| } | |
| GLenum type = uniform_info->type; | |
| uint32_t num_elements = GLES2Util::GetElementCountForUniformType(type); | |
| if (num_elements == 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glGetUniform", "unknown type"); | |
| return false; | |
| } | |
| uint32_t checked_size = 0; | |
| if (!SizedResult<T>::ComputeSize(num_elements).AssignIfValid(&checked_size)) { | |
| *error = error::kOutOfBounds; | |
| return false; | |
| } | |
| result = GetSharedMemoryAs<SizedResult<T>*>(shm_id, shm_offset, checked_size); | |
| if (!result) { | |
| *error = error::kOutOfBounds; | |
| return false; | |
| } | |
| result->SetNumResults(num_elements); | |
| *result_size = num_elements * sizeof(T); | |
| *result_type = type; | |
| return true; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetUniformiv( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::GetUniformiv& c = | |
| *static_cast<const volatile gles2::cmds::GetUniformiv*>(cmd_data); | |
| GLuint program = c.program; | |
| GLint fake_location = c.location; | |
| GLuint service_id; | |
| GLenum result_type; | |
| GLsizei result_size; | |
| GLint real_location = -1; | |
| Error error; | |
| cmds::GetUniformiv::Result* result; | |
| if (GetUniformSetup<GLint>(program, fake_location, c.params_shm_id, | |
| c.params_shm_offset, &error, &real_location, | |
| &service_id, &result, &result_type, | |
| &result_size)) { | |
| api()->glGetUniformivFn(service_id, real_location, result->GetData()); | |
| } | |
| return error; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetUniformuiv( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| if (!feature_info_->IsWebGL2OrES3Context()) | |
| return error::kUnknownCommand; | |
| const volatile gles2::cmds::GetUniformuiv& c = | |
| *static_cast<const volatile gles2::cmds::GetUniformuiv*>(cmd_data); | |
| GLuint program = c.program; | |
| GLint fake_location = c.location; | |
| GLuint service_id; | |
| GLenum result_type; | |
| GLsizei result_size; | |
| GLint real_location = -1; | |
| Error error; | |
| cmds::GetUniformuiv::Result* result; | |
| if (GetUniformSetup<GLuint>(program, fake_location, c.params_shm_id, | |
| c.params_shm_offset, &error, &real_location, | |
| &service_id, &result, &result_type, | |
| &result_size)) { | |
| api()->glGetUniformuivFn(service_id, real_location, result->GetData()); | |
| } | |
| return error; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetUniformfv( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::GetUniformfv& c = | |
| *static_cast<const volatile gles2::cmds::GetUniformfv*>(cmd_data); | |
| GLuint program = c.program; | |
| GLint fake_location = c.location; | |
| GLuint service_id; | |
| GLint real_location = -1; | |
| Error error; | |
| cmds::GetUniformfv::Result* result; | |
| GLenum result_type; | |
| GLsizei result_size; | |
| if (GetUniformSetup<GLfloat>(program, fake_location, c.params_shm_id, | |
| c.params_shm_offset, &error, &real_location, | |
| &service_id, &result, &result_type, | |
| &result_size)) { | |
| if (result_type == GL_BOOL || result_type == GL_BOOL_VEC2 || | |
| result_type == GL_BOOL_VEC3 || result_type == GL_BOOL_VEC4) { | |
| GLsizei num_values = result_size / sizeof(GLfloat); | |
| auto temp = base::HeapArray<GLint>::Uninit(num_values); | |
| api()->glGetUniformivFn(service_id, real_location, temp.data()); | |
| GLfloat* dst = result->GetData(); | |
| for (GLsizei ii = 0; ii < num_values; ++ii) { | |
| UNSAFE_TODO(dst[ii]) = (temp[ii] != 0); | |
| } | |
| } else { | |
| api()->glGetUniformfvFn(service_id, real_location, result->GetData()); | |
| } | |
| } | |
| return error; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetShaderPrecisionFormat( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::GetShaderPrecisionFormat& c = | |
| *static_cast<const volatile gles2::cmds::GetShaderPrecisionFormat*>( | |
| cmd_data); | |
| GLenum shader_type = static_cast<GLenum>(c.shadertype); | |
| GLenum precision_type = static_cast<GLenum>(c.precisiontype); | |
| typedef cmds::GetShaderPrecisionFormat::Result Result; | |
| Result* result = GetSharedMemoryAs<Result*>( | |
| c.result_shm_id, c.result_shm_offset, sizeof(*result)); | |
| if (!result) { | |
| return error::kOutOfBounds; | |
| } | |
| // Check that the client initialized the result. | |
| if (result->success != 0) { | |
| return error::kInvalidArguments; | |
| } | |
| if (!validators_->shader_type.IsValid(shader_type)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM( | |
| "glGetShaderPrecisionFormat", shader_type, "shader_type"); | |
| return error::kNoError; | |
| } | |
| if (!validators_->shader_precision.IsValid(precision_type)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM( | |
| "glGetShaderPrecisionFormat", precision_type, "precision_type"); | |
| return error::kNoError; | |
| } | |
| result->success = 1; // true | |
| GLint range[2] = { 0, 0 }; | |
| GLint precision = 0; | |
| QueryShaderPrecisionFormat(shader_type, precision_type, range, &precision); | |
| result->min_range = range[0]; | |
| result->max_range = range[1]; | |
| result->precision = precision; | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetAttachedShaders( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::GetAttachedShaders& c = | |
| *static_cast<const volatile gles2::cmds::GetAttachedShaders*>(cmd_data); | |
| uint32_t result_size = c.result_size; | |
| GLuint program_id = static_cast<GLuint>(c.program); | |
| Program* program = GetProgramInfoNotShader( | |
| program_id, "glGetAttachedShaders"); | |
| if (!program) { | |
| return error::kNoError; | |
| } | |
| typedef cmds::GetAttachedShaders::Result Result; | |
| uint32_t max_count = Result::ComputeMaxResults(result_size); | |
| uint32_t checked_size = 0; | |
| if (!Result::ComputeSize(max_count).AssignIfValid(&checked_size)) { | |
| return error::kOutOfBounds; | |
| } | |
| Result* result = GetSharedMemoryAs<Result*>( | |
| c.result_shm_id, c.result_shm_offset, checked_size); | |
| if (!result) { | |
| return error::kOutOfBounds; | |
| } | |
| // Check that the client initialized the result. | |
| if (result->size != 0) { | |
| return error::kInvalidArguments; | |
| } | |
| GLsizei count = 0; | |
| api()->glGetAttachedShadersFn(program->service_id(), max_count, &count, | |
| result->GetData()); | |
| for (GLsizei ii = 0; ii < count; ++ii) { | |
| if (!shader_manager()->GetClientId(UNSAFE_TODO(result->GetData()[ii]), | |
| UNSAFE_TODO(&result->GetData()[ii]))) { | |
| NOTREACHED(); | |
| } | |
| } | |
| result->SetNumResults(count); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetActiveUniform( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::GetActiveUniform& c = | |
| *static_cast<const volatile gles2::cmds::GetActiveUniform*>(cmd_data); | |
| GLuint program_id = c.program; | |
| GLuint index = c.index; | |
| uint32_t name_bucket_id = c.name_bucket_id; | |
| typedef cmds::GetActiveUniform::Result Result; | |
| Result* result = GetSharedMemoryAs<Result*>( | |
| c.result_shm_id, c.result_shm_offset, sizeof(*result)); | |
| if (!result) { | |
| return error::kOutOfBounds; | |
| } | |
| // Check that the client initialized the result. | |
| if (result->success != 0) { | |
| return error::kInvalidArguments; | |
| } | |
| Program* program = GetProgramInfoNotShader( | |
| program_id, "glGetActiveUniform"); | |
| if (!program) { | |
| return error::kNoError; | |
| } | |
| const Program::UniformInfo* uniform_info = | |
| program->GetUniformInfo(index); | |
| if (!uniform_info) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glGetActiveUniform", "index out of range"); | |
| return error::kNoError; | |
| } | |
| result->success = 1; // true. | |
| result->size = uniform_info->size; | |
| result->type = uniform_info->type; | |
| Bucket* bucket = CreateBucket(name_bucket_id); | |
| bucket->SetFromString(uniform_info->name.c_str()); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetActiveUniformBlockiv( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| if (!feature_info_->IsWebGL2OrES3Context()) | |
| return error::kUnknownCommand; | |
| const volatile gles2::cmds::GetActiveUniformBlockiv& c = | |
| *static_cast<const volatile gles2::cmds::GetActiveUniformBlockiv*>( | |
| cmd_data); | |
| GLuint program_id = c.program; | |
| GLuint index = static_cast<GLuint>(c.index); | |
| GLenum pname = static_cast<GLenum>(c.pname); | |
| Program* program = GetProgramInfoNotShader( | |
| program_id, "glGetActiveUniformBlockiv"); | |
| if (!program) { | |
| return error::kNoError; | |
| } | |
| GLuint service_id = program->service_id(); | |
| GLint link_status = GL_FALSE; | |
| api()->glGetProgramivFn(service_id, GL_LINK_STATUS, &link_status); | |
| if (link_status != GL_TRUE) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, | |
| "glGetActiveActiveUniformBlockiv", "program not linked"); | |
| return error::kNoError; | |
| } | |
| if (index >= program->uniform_block_size_info().size()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glGetActiveUniformBlockiv", | |
| "uniformBlockIndex >= active uniform blocks"); | |
| return error::kNoError; | |
| } | |
| GLsizei num_values = 1; | |
| if (pname == GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES) { | |
| GLint num = 0; | |
| api()->glGetActiveUniformBlockivFn(service_id, index, | |
| GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &num); | |
| GLenum error = api()->glGetErrorFn(); | |
| if (error != GL_NO_ERROR) { | |
| // Assume this will the same error if calling with pname. | |
| LOCAL_SET_GL_ERROR(error, "GetActiveUniformBlockiv", ""); | |
| return error::kNoError; | |
| } | |
| num_values = static_cast<GLsizei>(num); | |
| } | |
| typedef cmds::GetActiveUniformBlockiv::Result Result; | |
| uint32_t checked_size = 0; | |
| if (!Result::ComputeSize(num_values).AssignIfValid(&checked_size)) { | |
| return error::kOutOfBounds; | |
| } | |
| Result* result = GetSharedMemoryAs<Result*>( | |
| c.params_shm_id, c.params_shm_offset, checked_size); | |
| GLint* params = result ? result->GetData() : nullptr; | |
| if (params == nullptr) { | |
| return error::kOutOfBounds; | |
| } | |
| // Check that the client initialized the result. | |
| if (result->size != 0) { | |
| return error::kInvalidArguments; | |
| } | |
| api()->glGetActiveUniformBlockivFn(service_id, index, pname, params); | |
| result->SetNumResults(num_values); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetActiveUniformBlockName( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| if (!feature_info_->IsWebGL2OrES3Context()) | |
| return error::kUnknownCommand; | |
| const volatile gles2::cmds::GetActiveUniformBlockName& c = | |
| *static_cast<const volatile gles2::cmds::GetActiveUniformBlockName*>( | |
| cmd_data); | |
| GLuint program_id = c.program; | |
| GLuint index = c.index; | |
| uint32_t name_bucket_id = c.name_bucket_id; | |
| typedef cmds::GetActiveUniformBlockName::Result Result; | |
| Result* result = GetSharedMemoryAs<Result*>( | |
| c.result_shm_id, c.result_shm_offset, sizeof(*result)); | |
| if (!result) { | |
| return error::kOutOfBounds; | |
| } | |
| // Check that the client initialized the result. | |
| if (*result != 0) { | |
| return error::kInvalidArguments; | |
| } | |
| Program* program = GetProgramInfoNotShader( | |
| program_id, "glGetActiveUniformBlockName"); | |
| if (!program) { | |
| return error::kNoError; | |
| } | |
| GLuint service_id = program->service_id(); | |
| GLint link_status = GL_FALSE; | |
| api()->glGetProgramivFn(service_id, GL_LINK_STATUS, &link_status); | |
| if (link_status != GL_TRUE) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, | |
| "glGetActiveActiveUniformBlockName", "program not linked"); | |
| return error::kNoError; | |
| } | |
| if (index >= program->uniform_block_size_info().size()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glGetActiveUniformBlockName", | |
| "uniformBlockIndex >= active uniform blocks"); | |
| return error::kNoError; | |
| } | |
| GLint max_length = 0; | |
| api()->glGetProgramivFn(service_id, GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH, | |
| &max_length); | |
| // Increase one so &buffer[0] is always valid. | |
| GLsizei buf_size = static_cast<GLsizei>(max_length) + 1; | |
| std::vector<char> buffer(buf_size); | |
| GLsizei length = 0; | |
| api()->glGetActiveUniformBlockNameFn(service_id, index, buf_size, &length, | |
| &buffer[0]); | |
| if (length == 0) { | |
| *result = 0; | |
| return error::kNoError; | |
| } | |
| *result = 1; | |
| Bucket* bucket = CreateBucket(name_bucket_id); | |
| DCHECK_GT(buf_size, length); | |
| DCHECK_EQ(0, buffer[length]); | |
| bucket->SetFromString(&buffer[0]); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetActiveUniformsiv( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| if (!feature_info_->IsWebGL2OrES3Context()) | |
| return error::kUnknownCommand; | |
| const volatile gles2::cmds::GetActiveUniformsiv& c = | |
| *static_cast<const volatile gles2::cmds::GetActiveUniformsiv*>(cmd_data); | |
| GLuint program_id = c.program; | |
| GLenum pname = static_cast<GLenum>(c.pname); | |
| Bucket* bucket = GetBucket(c.indices_bucket_id); | |
| if (!bucket) { | |
| return error::kInvalidArguments; | |
| } | |
| if (!validators_->uniform_parameter.IsValid(pname)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM("glGetActiveUniformsiv", pname, "pname"); | |
| return error::kNoError; | |
| } | |
| GLsizei count = static_cast<GLsizei>(bucket->size() / sizeof(GLuint)); | |
| base::span<GLuint> indices = bucket->GetDataAsSpan<GLuint>(0, count); | |
| typedef cmds::GetActiveUniformsiv::Result Result; | |
| uint32_t checked_size = 0; | |
| if (!Result::ComputeSize(count).AssignIfValid(&checked_size)) { | |
| return error::kOutOfBounds; | |
| } | |
| Result* result = GetSharedMemoryAs<Result*>( | |
| c.params_shm_id, c.params_shm_offset, checked_size); | |
| GLint* params = result ? result->GetData() : nullptr; | |
| if (params == nullptr) { | |
| return error::kOutOfBounds; | |
| } | |
| // Check that the client initialized the result. | |
| if (result->size != 0) { | |
| return error::kInvalidArguments; | |
| } | |
| Program* program = GetProgramInfoNotShader( | |
| program_id, "glGetActiveUniformsiv"); | |
| if (!program) { | |
| return error::kNoError; | |
| } | |
| GLint activeUniforms = 0; | |
| program->GetProgramiv(GL_ACTIVE_UNIFORMS, &activeUniforms); | |
| for (const GLuint index : indices) { | |
| if (index >= static_cast<GLuint>(activeUniforms)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, | |
| "glGetActiveUniformsiv", "index >= active uniforms"); | |
| return error::kNoError; | |
| } | |
| } | |
| GLuint service_id = program->service_id(); | |
| GLint link_status = GL_FALSE; | |
| api()->glGetProgramivFn(service_id, GL_LINK_STATUS, &link_status); | |
| if (link_status != GL_TRUE) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, | |
| "glGetActiveUniformsiv", "program not linked"); | |
| return error::kNoError; | |
| } | |
| api()->glGetActiveUniformsivFn(service_id, count, indices.data(), pname, | |
| params); | |
| result->SetNumResults(count); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetActiveAttrib( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::GetActiveAttrib& c = | |
| *static_cast<const volatile gles2::cmds::GetActiveAttrib*>(cmd_data); | |
| GLuint program_id = c.program; | |
| GLuint index = c.index; | |
| uint32_t name_bucket_id = c.name_bucket_id; | |
| typedef cmds::GetActiveAttrib::Result Result; | |
| Result* result = GetSharedMemoryAs<Result*>( | |
| c.result_shm_id, c.result_shm_offset, sizeof(*result)); | |
| if (!result) { | |
| return error::kOutOfBounds; | |
| } | |
| // Check that the client initialized the result. | |
| if (result->success != 0) { | |
| return error::kInvalidArguments; | |
| } | |
| Program* program = GetProgramInfoNotShader( | |
| program_id, "glGetActiveAttrib"); | |
| if (!program) { | |
| return error::kNoError; | |
| } | |
| const Program::VertexAttrib* attrib_info = | |
| program->GetAttribInfo(index); | |
| if (!attrib_info) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, "glGetActiveAttrib", "index out of range"); | |
| return error::kNoError; | |
| } | |
| result->success = 1; // true. | |
| result->size = attrib_info->size; | |
| result->type = attrib_info->type; | |
| Bucket* bucket = CreateBucket(name_bucket_id); | |
| bucket->SetFromString(attrib_info->name.c_str()); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleShaderBinary( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| #if 1 // No binary shader support. | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glShaderBinary", "not supported"); | |
| return error::kNoError; | |
| #else | |
| GLsizei n = static_cast<GLsizei>(c.n); | |
| if (n < 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glShaderBinary", "n < 0"); | |
| return error::kNoError; | |
| } | |
| GLsizei length = static_cast<GLsizei>(c.length); | |
| if (length < 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glShaderBinary", "length < 0"); | |
| return error::kNoError; | |
| } | |
| uint32_t data_size; | |
| if (!base::CheckMul(n, sizeof(GLuint)).AssignIfValid(&data_size)) { | |
| return error::kOutOfBounds; | |
| } | |
| const GLuint* shaders = GetSharedMemoryAs<const GLuint*>( | |
| c.shaders_shm_id, c.shaders_shm_offset, data_size); | |
| GLenum binaryformat = static_cast<GLenum>(c.binaryformat); | |
| const void* binary = GetSharedMemoryAs<const void*>( | |
| c.binary_shm_id, c.binary_shm_offset, length); | |
| if (shaders == nullptr || binary == nullptr) { | |
| return error::kOutOfBounds; | |
| } | |
| auto service_ids = base::HeapArray<GLuint>::Uninit(n); | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| Shader* shader = GetShader(shaders[ii]); | |
| if (!shader) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glShaderBinary", "unknown shader"); | |
| return error::kNoError; | |
| } | |
| service_ids[ii] = shader->service_id(); | |
| } | |
| // TODO(gman): call glShaderBinary | |
| return error::kNoError; | |
| #endif | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetRequestableExtensionsCHROMIUM( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::GetRequestableExtensionsCHROMIUM& c = | |
| *static_cast< | |
| const volatile gles2::cmds::GetRequestableExtensionsCHROMIUM*>( | |
| cmd_data); | |
| Bucket* bucket = CreateBucket(c.bucket_id); | |
| auto info = base::MakeRefCounted<FeatureInfo>(workarounds(), | |
| group_->gpu_feature_info()); | |
| DisallowedFeatures disallowed_features = feature_info_->disallowed_features(); | |
| disallowed_features.AllowExtensions(); | |
| info->InitializeWithCompleteFramebufferForWorkarounds( | |
| feature_info_->context_type(), false /* is_passthrough_cmd_decoder */, | |
| disallowed_features, complete_fbo_); | |
| bucket->SetFromString(gfx::MakeExtensionString(info->extensions()).c_str()); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleRequestExtensionCHROMIUM( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::RequestExtensionCHROMIUM& c = | |
| *static_cast<const volatile gles2::cmds::RequestExtensionCHROMIUM*>( | |
| cmd_data); | |
| Bucket* bucket = GetBucket(c.bucket_id); | |
| if (!bucket || bucket->size() == 0) { | |
| return error::kInvalidArguments; | |
| } | |
| std::string feature_str; | |
| if (!bucket->GetAsString(&feature_str)) { | |
| return error::kInvalidArguments; | |
| } | |
| feature_str = feature_str + " "; | |
| bool desire_standard_derivatives = false; | |
| bool desire_fbo_render_mipmap = false; | |
| bool desire_frag_depth = false; | |
| bool desire_draw_buffers = false; | |
| bool desire_shader_texture_lod = false; | |
| bool desire_multi_draw = false; | |
| bool desire_draw_instanced_base_vertex_base_instance = false; | |
| bool desire_multi_draw_instanced_base_vertex_base_instance = false; | |
| bool desire_arb_texture_rectangle = false; | |
| bool desire_oes_egl_image_external = false; | |
| bool desire_nv_egl_stream_consumer_external = false; | |
| if (feature_info_->context_type() == CONTEXT_TYPE_WEBGL1) { | |
| desire_standard_derivatives = | |
| feature_str.contains("GL_OES_standard_derivatives "); | |
| desire_fbo_render_mipmap = | |
| feature_str.contains("GL_OES_fbo_render_mipmap "); | |
| desire_frag_depth = feature_str.contains("GL_EXT_frag_depth "); | |
| desire_draw_buffers = feature_str.contains("GL_EXT_draw_buffers "); | |
| desire_shader_texture_lod = | |
| feature_str.contains("GL_EXT_shader_texture_lod "); | |
| } else if (feature_info_->context_type() == CONTEXT_TYPE_WEBGL2) { | |
| desire_draw_instanced_base_vertex_base_instance = feature_str.contains( | |
| "GL_WEBGL_draw_instanced_base_vertex_base_instance "); | |
| desire_multi_draw_instanced_base_vertex_base_instance = | |
| feature_str.contains( | |
| "GL_WEBGL_multi_draw_instanced_base_vertex_base_instance "); | |
| } | |
| if (feature_info_->IsWebGLContext()) { | |
| desire_multi_draw = feature_str.contains("GL_WEBGL_multi_draw "); | |
| desire_arb_texture_rectangle = | |
| feature_str.contains("GL_ANGLE_texture_rectangle "); | |
| desire_oes_egl_image_external = | |
| feature_str.contains("GL_OES_EGL_image_external "); | |
| desire_nv_egl_stream_consumer_external = | |
| feature_str.contains("GL_NV_EGL_stream_consumer_external "); | |
| } | |
| if (desire_standard_derivatives != derivatives_explicitly_enabled_ || | |
| desire_fbo_render_mipmap != fbo_render_mipmap_explicitly_enabled_ || | |
| desire_frag_depth != frag_depth_explicitly_enabled_ || | |
| desire_draw_buffers != draw_buffers_explicitly_enabled_ || | |
| desire_shader_texture_lod != shader_texture_lod_explicitly_enabled_ || | |
| desire_multi_draw != multi_draw_explicitly_enabled_ || | |
| desire_draw_instanced_base_vertex_base_instance != | |
| draw_instanced_base_vertex_base_instance_explicitly_enabled_ || | |
| desire_multi_draw_instanced_base_vertex_base_instance != | |
| multi_draw_instanced_base_vertex_base_instance_explicitly_enabled_ || | |
| desire_arb_texture_rectangle != arb_texture_rectangle_enabled_ || | |
| desire_oes_egl_image_external != oes_egl_image_external_enabled_ || | |
| desire_nv_egl_stream_consumer_external != | |
| nv_egl_stream_consumer_external_enabled_) { | |
| derivatives_explicitly_enabled_ |= desire_standard_derivatives; | |
| fbo_render_mipmap_explicitly_enabled_ |= desire_fbo_render_mipmap; | |
| frag_depth_explicitly_enabled_ |= desire_frag_depth; | |
| draw_buffers_explicitly_enabled_ |= desire_draw_buffers; | |
| shader_texture_lod_explicitly_enabled_ |= desire_shader_texture_lod; | |
| multi_draw_explicitly_enabled_ |= desire_multi_draw; | |
| draw_instanced_base_vertex_base_instance_explicitly_enabled_ |= | |
| desire_draw_instanced_base_vertex_base_instance; | |
| multi_draw_instanced_base_vertex_base_instance_explicitly_enabled_ |= | |
| desire_multi_draw_instanced_base_vertex_base_instance; | |
| arb_texture_rectangle_enabled_ |= desire_arb_texture_rectangle; | |
| oes_egl_image_external_enabled_ |= desire_oes_egl_image_external; | |
| nv_egl_stream_consumer_external_enabled_ |= | |
| desire_nv_egl_stream_consumer_external; | |
| DestroyShaderTranslator(); | |
| } | |
| if (feature_str.contains("GL_CHROMIUM_color_buffer_float_rgba ")) { | |
| feature_info_->EnableCHROMIUMColorBufferFloatRGBA(); | |
| } | |
| if (feature_str.contains("GL_CHROMIUM_color_buffer_float_rgb ")) { | |
| feature_info_->EnableCHROMIUMColorBufferFloatRGB(); | |
| } | |
| if (feature_str.contains("GL_EXT_color_buffer_float ")) { | |
| feature_info_->EnableEXTColorBufferFloat(); | |
| } | |
| if (feature_str.contains("GL_EXT_color_buffer_half_float ")) { | |
| feature_info_->EnableEXTColorBufferHalfFloat(); | |
| } | |
| if (feature_str.contains("GL_EXT_texture_filter_anisotropic ")) { | |
| feature_info_->EnableEXTTextureFilterAnisotropic(); | |
| } | |
| if (feature_str.contains("GL_OES_texture_float_linear ")) { | |
| feature_info_->EnableOESTextureFloatLinear(); | |
| } | |
| if (feature_str.contains("GL_OES_texture_half_float_linear ")) { | |
| feature_info_->EnableOESTextureHalfFloatLinear(); | |
| } | |
| if (feature_str.contains("GL_EXT_float_blend ")) { | |
| feature_info_->EnableEXTFloatBlend(); | |
| } | |
| if (feature_str.contains("GL_OES_fbo_render_mipmap ")) { | |
| feature_info_->EnableOESFboRenderMipmap(); | |
| } | |
| if (feature_str.contains("GL_ANGLE_compressed_texture_etc ")) { | |
| feature_info_->EnableWebGLCompressedTextureETC(); | |
| } | |
| if (feature_str.contains("GL_OES_compressed_ETC1_RGB8_texture ")) { | |
| feature_info_->EnableWebGLCompressedTextureETC1(); | |
| } | |
| UpdateCapabilities(); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetProgramInfoCHROMIUM( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::GetProgramInfoCHROMIUM& c = | |
| *static_cast<const volatile gles2::cmds::GetProgramInfoCHROMIUM*>( | |
| cmd_data); | |
| GLuint program_id = static_cast<GLuint>(c.program); | |
| uint32_t bucket_id = c.bucket_id; | |
| Bucket* bucket = CreateBucket(bucket_id); | |
| bucket->SetSize(sizeof(ProgramInfoHeader)); // in case we fail. | |
| Program* program = nullptr; | |
| program = GetProgram(program_id); | |
| if (!program || !program->IsValid()) { | |
| return error::kNoError; | |
| } | |
| program->GetProgramInfo(program_manager(), bucket); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetUniformBlocksCHROMIUM( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| if (!feature_info_->IsWebGL2OrES3Context()) | |
| return error::kUnknownCommand; | |
| const volatile gles2::cmds::GetUniformBlocksCHROMIUM& c = | |
| *static_cast<const volatile gles2::cmds::GetUniformBlocksCHROMIUM*>( | |
| cmd_data); | |
| GLuint program_id = static_cast<GLuint>(c.program); | |
| uint32_t bucket_id = c.bucket_id; | |
| Bucket* bucket = CreateBucket(bucket_id); | |
| bucket->SetSize(sizeof(UniformBlocksHeader)); // in case we fail. | |
| Program* program = nullptr; | |
| program = GetProgram(program_id); | |
| if (!program || !program->IsValid()) { | |
| return error::kNoError; | |
| } | |
| program->GetUniformBlocks(bucket); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetUniformsES3CHROMIUM( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| if (!feature_info_->IsWebGL2OrES3Context()) | |
| return error::kUnknownCommand; | |
| const volatile gles2::cmds::GetUniformsES3CHROMIUM& c = | |
| *static_cast<const volatile gles2::cmds::GetUniformsES3CHROMIUM*>( | |
| cmd_data); | |
| GLuint program_id = static_cast<GLuint>(c.program); | |
| uint32_t bucket_id = c.bucket_id; | |
| Bucket* bucket = CreateBucket(bucket_id); | |
| bucket->SetSize(sizeof(UniformsES3Header)); // in case we fail. | |
| Program* program = nullptr; | |
| program = GetProgram(program_id); | |
| if (!program || !program->IsValid()) { | |
| return error::kNoError; | |
| } | |
| program->GetUniformsES3(bucket); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetTransformFeedbackVarying( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| if (!feature_info_->IsWebGL2OrES3Context()) | |
| return error::kUnknownCommand; | |
| const volatile gles2::cmds::GetTransformFeedbackVarying& c = | |
| *static_cast<const volatile gles2::cmds::GetTransformFeedbackVarying*>( | |
| cmd_data); | |
| GLuint program_id = c.program; | |
| GLuint index = c.index; | |
| uint32_t name_bucket_id = c.name_bucket_id; | |
| typedef cmds::GetTransformFeedbackVarying::Result Result; | |
| Result* result = GetSharedMemoryAs<Result*>( | |
| c.result_shm_id, c.result_shm_offset, sizeof(*result)); | |
| if (!result) { | |
| return error::kOutOfBounds; | |
| } | |
| // Check that the client initialized the result. | |
| if (result->success != 0) { | |
| return error::kInvalidArguments; | |
| } | |
| Program* program = GetProgramInfoNotShader( | |
| program_id, "glGetTransformFeedbackVarying"); | |
| if (!program) { | |
| // An error is already set. | |
| return error::kNoError; | |
| } | |
| GLuint service_id = program->service_id(); | |
| GLint link_status = GL_FALSE; | |
| api()->glGetProgramivFn(service_id, GL_LINK_STATUS, &link_status); | |
| if (link_status != GL_TRUE) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, | |
| "glGetTransformFeedbackVarying", "program not linked"); | |
| return error::kNoError; | |
| } | |
| GLint num_varyings = 0; | |
| api()->glGetProgramivFn(service_id, GL_TRANSFORM_FEEDBACK_VARYINGS, | |
| &num_varyings); | |
| if (index >= static_cast<GLuint>(num_varyings)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, | |
| "glGetTransformFeedbackVarying", "index out of bounds"); | |
| return error::kNoError; | |
| } | |
| GLint max_length = 0; | |
| api()->glGetProgramivFn(service_id, GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH, | |
| &max_length); | |
| max_length = std::max(1, max_length); | |
| std::vector<char> buffer(max_length); | |
| GLsizei length = 0; | |
| GLsizei size = 0; | |
| GLenum type = 0; | |
| api()->glGetTransformFeedbackVaryingFn(service_id, index, max_length, &length, | |
| &size, &type, &buffer[0]); | |
| result->success = 1; // true. | |
| result->size = static_cast<int32_t>(size); | |
| result->type = static_cast<uint32_t>(type); | |
| Bucket* bucket = CreateBucket(name_bucket_id); | |
| DCHECK(length >= 0 && length < max_length); | |
| buffer[length] = '\0'; // Just to be safe. | |
| bucket->SetFromString(&buffer[0]); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetTransformFeedbackVaryingsCHROMIUM( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| if (!feature_info_->IsWebGL2OrES3Context()) | |
| return error::kUnknownCommand; | |
| const volatile gles2::cmds::GetTransformFeedbackVaryingsCHROMIUM& c = | |
| *static_cast< | |
| const volatile gles2::cmds::GetTransformFeedbackVaryingsCHROMIUM*>( | |
| cmd_data); | |
| GLuint program_id = static_cast<GLuint>(c.program); | |
| uint32_t bucket_id = c.bucket_id; | |
| Bucket* bucket = CreateBucket(bucket_id); | |
| bucket->SetSize(sizeof(TransformFeedbackVaryingsHeader)); // in case we fail. | |
| Program* program = nullptr; | |
| program = GetProgram(program_id); | |
| if (!program || !program->IsValid()) { | |
| return error::kNoError; | |
| } | |
| program->GetTransformFeedbackVaryings(bucket); | |
| return error::kNoError; | |
| } | |
| bool GLES2DecoderImpl::WasContextLost() const { | |
| return context_was_lost_; | |
| } | |
| bool GLES2DecoderImpl::WasContextLostByRobustnessExtension() const { | |
| return WasContextLost() && reset_by_robustness_extension_; | |
| } | |
| void GLES2DecoderImpl::MarkContextLost(error::ContextLostReason reason) { | |
| // Only lose the context once. | |
| if (WasContextLost()) | |
| return; | |
| // Don't make GL calls in here, the context might not be current. | |
| command_buffer_service()->SetContextLostReason(reason); | |
| current_decoder_error_ = error::kLostContext; | |
| context_was_lost_ = true; | |
| if (transform_feedback_manager_.get()) { | |
| transform_feedback_manager_->MarkContextLost(); | |
| } | |
| if (vertex_array_manager_.get()) { | |
| vertex_array_manager_->MarkContextLost(); | |
| } | |
| state_.MarkContextLost(); | |
| } | |
| bool GLES2DecoderImpl::CheckResetStatus() { | |
| DCHECK(!WasContextLost()); | |
| DCHECK(context_->IsCurrent(nullptr)); | |
| // If the reason for the call was a GL error, we can try to determine the | |
| // reset status more accurately. | |
| GLenum driver_status = context_->CheckStickyGraphicsResetStatus(); | |
| if (driver_status == GL_NO_ERROR) | |
| return false; | |
| LOG(ERROR) << (surface_->IsOffscreen() ? "Offscreen" : "Onscreen") | |
| << " context lost via ARB/EXT_robustness. Reset status = " | |
| << GLES2Util::GetStringEnum(driver_status); | |
| switch (driver_status) { | |
| case GL_GUILTY_CONTEXT_RESET: | |
| MarkContextLost(error::kGuilty); | |
| break; | |
| case GL_INNOCENT_CONTEXT_RESET: | |
| MarkContextLost(error::kInnocent); | |
| break; | |
| case GL_UNKNOWN_CONTEXT_RESET: | |
| MarkContextLost(error::kUnknown); | |
| break; | |
| default: | |
| NOTREACHED(); | |
| } | |
| reset_by_robustness_extension_ = true; | |
| return true; | |
| } | |
| error::Error GLES2DecoderImpl::HandleDescheduleUntilFinishedCHROMIUM( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| if (!gl::GLFence::IsSupported()) | |
| return error::kNoError; | |
| std::unique_ptr<gl::GLFence> fence = gl::GLFence::Create(); | |
| if (fence) | |
| deschedule_until_finished_fences_.push_back(std::move(fence)); | |
| if (deschedule_until_finished_fences_.size() <= 1) | |
| return error::kNoError; | |
| DCHECK_EQ(2u, deschedule_until_finished_fences_.size()); | |
| if (deschedule_until_finished_fences_[0]->HasCompleted()) { | |
| deschedule_until_finished_fences_.erase( | |
| deschedule_until_finished_fences_.begin()); | |
| return error::kNoError; | |
| } | |
| TRACE_EVENT_BEGIN( | |
| "cc", "GLES2DecoderImpl::DescheduleUntilFinished", | |
| perfetto::NamedTrack::FromPointer("gpu::gles2::GLES2DecoderImpl", this)); | |
| client()->OnDescheduleUntilFinished(); | |
| return error::kDeferLaterCommands; | |
| } | |
| bool GLES2DecoderImpl::GenQueriesEXTHelper( | |
| GLsizei n, const GLuint* client_ids) { | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| if (query_manager_->IsValidQuery(UNSAFE_TODO(client_ids[ii]))) { | |
| return false; | |
| } | |
| } | |
| query_manager_->GenQueries(n, client_ids); | |
| return true; | |
| } | |
| void GLES2DecoderImpl::DeleteQueriesEXTHelper( | |
| GLsizei n, | |
| const volatile GLuint* client_ids) { | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| GLuint client_id = UNSAFE_TODO(client_ids[ii]); | |
| query_manager_->RemoveQuery(client_id); | |
| } | |
| } | |
| void GLES2DecoderImpl::SetQueryCallback(unsigned int query_client_id, | |
| base::OnceClosure callback) { | |
| QueryManager::Query* query = query_manager_->GetQuery(query_client_id); | |
| if (query) { | |
| query->AddCallback(std::move(callback)); | |
| } else { | |
| VLOG(1) << "GLES2DecoderImpl::SetQueryCallback: No query with ID " | |
| << query_client_id << ". Running the callback immediately."; | |
| std::move(callback).Run(); | |
| } | |
| } | |
| void GLES2DecoderImpl::CancelAllQueries() { | |
| query_manager_->RemoveAllQueries(); | |
| } | |
| bool GLES2DecoderImpl::HasPendingQueries() const { | |
| return query_manager_.get() && query_manager_->HavePendingQueries(); | |
| } | |
| void GLES2DecoderImpl::ProcessPendingQueries(bool did_finish) { | |
| if (!query_manager_.get()) | |
| return; | |
| query_manager_->ProcessPendingQueries(did_finish); | |
| } | |
| // Note that if there are no pending readpixels right now, | |
| // this function will call the callback immediately. | |
| void GLES2DecoderImpl::WaitForReadPixels(base::OnceClosure callback) { | |
| if (features().use_async_readpixels && !pending_readpixel_fences_.empty()) { | |
| pending_readpixel_fences_.back().callbacks.push_back(std::move(callback)); | |
| } else { | |
| std::move(callback).Run(); | |
| } | |
| } | |
| void GLES2DecoderImpl::ProcessPendingReadPixels(bool did_finish) { | |
| // Note: |did_finish| guarantees that the GPU has passed the fence but | |
| // we cannot assume that GLFence::HasCompleted() will return true yet as | |
| // that's not guaranteed by all GLFence implementations. | |
| while (!pending_readpixel_fences_.empty() && | |
| (did_finish || | |
| pending_readpixel_fences_.front().fence->HasCompleted())) { | |
| std::vector<base::OnceClosure> callbacks = | |
| std::move(pending_readpixel_fences_.front().callbacks); | |
| pending_readpixel_fences_.pop(); | |
| for (size_t i = 0; i < callbacks.size(); i++) { | |
| std::move(callbacks[i]).Run(); | |
| } | |
| } | |
| } | |
| void GLES2DecoderImpl::ProcessDescheduleUntilFinished() { | |
| if (deschedule_until_finished_fences_.size() < 2) | |
| return; | |
| DCHECK_EQ(2u, deschedule_until_finished_fences_.size()); | |
| if (!deschedule_until_finished_fences_[0]->HasCompleted()) | |
| return; | |
| TRACE_EVENT_END("cc", perfetto::NamedTrack::FromPointer( | |
| "gpu::gles2::GLES2DecoderImpl", this)); | |
| deschedule_until_finished_fences_.erase( | |
| deschedule_until_finished_fences_.begin()); | |
| client()->OnRescheduleAfterFinished(); | |
| } | |
| bool GLES2DecoderImpl::HasMoreIdleWork() const { | |
| bool has_more_idle_work = | |
| !pending_readpixel_fences_.empty() || gpu_tracer_->HasTracesToProcess(); | |
| return has_more_idle_work; | |
| } | |
| void GLES2DecoderImpl::PerformIdleWork() { | |
| gpu_tracer_->ProcessTraces(); | |
| ProcessPendingReadPixels(false); | |
| } | |
| bool GLES2DecoderImpl::HasPollingWork() const { | |
| return deschedule_until_finished_fences_.size() >= 2; | |
| } | |
| void GLES2DecoderImpl::PerformPollingWork() { | |
| ProcessDescheduleUntilFinished(); | |
| } | |
| error::Error GLES2DecoderImpl::HandleBeginQueryEXT( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::BeginQueryEXT& c = | |
| *static_cast<const volatile gles2::cmds::BeginQueryEXT*>(cmd_data); | |
| GLenum target = static_cast<GLenum>(c.target); | |
| GLuint client_id = static_cast<GLuint>(c.id); | |
| int32_t sync_shm_id = static_cast<int32_t>(c.sync_data_shm_id); | |
| uint32_t sync_shm_offset = static_cast<uint32_t>(c.sync_data_shm_offset); | |
| switch (target) { | |
| case GL_COMMANDS_ISSUED_CHROMIUM: | |
| case GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM: | |
| case GL_GET_ERROR_QUERY_CHROMIUM: | |
| break; | |
| case GL_READBACK_SHADOW_COPIES_UPDATED_CHROMIUM: | |
| case GL_COMMANDS_COMPLETED_CHROMIUM: | |
| if (!features().chromium_sync_query) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, "glBeginQueryEXT", | |
| "not enabled for commands completed queries"); | |
| return error::kNoError; | |
| } | |
| break; | |
| case GL_PROGRAM_COMPLETION_QUERY_CHROMIUM: | |
| if (!features().chromium_completion_query) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glBeginQueryEXT", | |
| "not enabled for program completion queries"); | |
| return error::kNoError; | |
| } | |
| break; | |
| case GL_SAMPLES_PASSED_ARB: | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glBeginQueryEXT", | |
| "not enabled for boolean occlusion queries"); | |
| return error::kNoError; | |
| case GL_ANY_SAMPLES_PASSED: | |
| case GL_ANY_SAMPLES_PASSED_CONSERVATIVE: | |
| if (!features().occlusion_query_boolean) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glBeginQueryEXT", | |
| "not enabled for boolean occlusion queries"); | |
| return error::kNoError; | |
| } | |
| break; | |
| case GL_TIME_ELAPSED_EXT: | |
| if (!query_manager_->GPUTimingAvailable()) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, "glBeginQueryEXT", | |
| "not enabled for timing queries"); | |
| return error::kNoError; | |
| } | |
| break; | |
| case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN: | |
| if (feature_info_->IsWebGL2OrES3Context()) { | |
| break; | |
| } | |
| [[fallthrough]]; | |
| default: | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_ENUM, "glBeginQueryEXT", | |
| "unknown query target"); | |
| return error::kNoError; | |
| } | |
| if (query_manager_->GetActiveQuery(target)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, "glBeginQueryEXT", "query already in progress"); | |
| return error::kNoError; | |
| } | |
| if (client_id == 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glBeginQueryEXT", "id is 0"); | |
| return error::kNoError; | |
| } | |
| scoped_refptr<gpu::Buffer> buffer = GetSharedMemoryBuffer(sync_shm_id); | |
| if (!buffer) | |
| return error::kInvalidArguments; | |
| QuerySync* sync = static_cast<QuerySync*>( | |
| buffer->GetDataAddress(sync_shm_offset, sizeof(QuerySync))); | |
| if (!sync) | |
| return error::kOutOfBounds; | |
| QueryManager::Query* query = query_manager_->GetQuery(client_id); | |
| if (!query) { | |
| if (!query_manager_->IsValidQuery(client_id)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, | |
| "glBeginQueryEXT", | |
| "id not made by glGenQueriesEXT"); | |
| return error::kNoError; | |
| } | |
| query = | |
| query_manager_->CreateQuery(target, client_id, std::move(buffer), sync); | |
| } else { | |
| if (query->target() != target) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glBeginQueryEXT", | |
| "target does not match"); | |
| return error::kNoError; | |
| } else if (query->sync() != sync) { | |
| DLOG(ERROR) << "Shared memory used by query not the same as before"; | |
| return error::kInvalidArguments; | |
| } | |
| } | |
| query_manager_->BeginQuery(query); | |
| return error::kNoError; | |
| } | |
| void GLES2DecoderImpl::ReadBackBuffersIntoShadowCopies( | |
| base::flat_set<scoped_refptr<Buffer>> buffers_to_shadow_copy) { | |
| GLuint old_binding = | |
| state_.bound_array_buffer ? state_.bound_array_buffer->service_id() : 0; | |
| for (scoped_refptr<Buffer>& buffer : buffers_to_shadow_copy) { | |
| if (buffer->IsDeleted()) { | |
| continue; | |
| } | |
| void* shadow = nullptr; | |
| scoped_refptr<gpu::Buffer> gpu_buffer = | |
| buffer->TakeReadbackShadowAllocation(&shadow); | |
| if (!shadow) { | |
| continue; | |
| } | |
| api()->glBindBufferFn(GL_ARRAY_BUFFER, buffer->service_id()); | |
| void* mapped = api()->glMapBufferRangeFn(GL_ARRAY_BUFFER, 0, buffer->size(), | |
| GL_MAP_READ_BIT); | |
| if (!mapped) { | |
| DLOG(ERROR) << "glMapBufferRange unexpectedly returned nullptr"; | |
| MarkContextLost(error::kOutOfMemory); | |
| group_->LoseContexts(error::kUnknown); | |
| return; | |
| } | |
| UNSAFE_TODO(memcpy(shadow, mapped, buffer->size())); | |
| bool unmap_ok = api()->glUnmapBufferFn(GL_ARRAY_BUFFER); | |
| if (unmap_ok == GL_FALSE) { | |
| DLOG(ERROR) << "glUnmapBuffer unexpectedly returned GL_FALSE"; | |
| MarkContextLost(error::kUnknown); | |
| group_->LoseContexts(error::kUnknown); | |
| return; | |
| } | |
| } | |
| // Restore original GL_ARRAY_BUFFER binding | |
| api()->glBindBufferFn(GL_ARRAY_BUFFER, old_binding); | |
| } | |
| error::Error GLES2DecoderImpl::HandleEndQueryEXT( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::EndQueryEXT& c = | |
| *static_cast<const volatile gles2::cmds::EndQueryEXT*>(cmd_data); | |
| GLenum target = static_cast<GLenum>(c.target); | |
| uint32_t submit_count = static_cast<GLuint>(c.submit_count); | |
| QueryManager::Query* query = query_manager_->GetActiveQuery(target); | |
| if (!query) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, "glEndQueryEXT", "No active query"); | |
| return error::kNoError; | |
| } | |
| if (target == GL_READBACK_SHADOW_COPIES_UPDATED_CHROMIUM && | |
| !writes_submitted_but_not_completed_.empty()) { | |
| // - This callback will be called immediately by MarkAsCompleted, so it's | |
| // okay that it's called after UnmarkAsPending. (It's guaranteed to not be | |
| // called immediately here, because the query has not even ended yet.) | |
| // | |
| // - It's okay to capture Unretained(this) because the callback is called by | |
| // the query, which is owned by query_manager_, which is owned by `this`. | |
| query->AddCallback( | |
| base::BindOnce(&GLES2DecoderImpl::ReadBackBuffersIntoShadowCopies, | |
| base::Unretained(this), | |
| std::move(writes_submitted_but_not_completed_))); | |
| writes_submitted_but_not_completed_.clear(); | |
| } | |
| query_manager_->EndQuery(query, submit_count); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleQueryCounterEXT( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::QueryCounterEXT& c = | |
| *static_cast<const volatile gles2::cmds::QueryCounterEXT*>(cmd_data); | |
| GLuint client_id = static_cast<GLuint>(c.id); | |
| GLenum target = static_cast<GLenum>(c.target); | |
| int32_t sync_shm_id = static_cast<int32_t>(c.sync_data_shm_id); | |
| uint32_t sync_shm_offset = static_cast<uint32_t>(c.sync_data_shm_offset); | |
| uint32_t submit_count = static_cast<GLuint>(c.submit_count); | |
| switch (target) { | |
| case GL_TIMESTAMP_EXT: | |
| if (!query_manager_->GPUTimingAvailable()) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, "glQueryCounterEXT", | |
| "not enabled for timing queries"); | |
| return error::kNoError; | |
| } | |
| break; | |
| default: | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_ENUM, "glQueryCounterEXT", | |
| "unknown query target"); | |
| return error::kNoError; | |
| } | |
| scoped_refptr<gpu::Buffer> buffer = GetSharedMemoryBuffer(sync_shm_id); | |
| if (!buffer) | |
| return error::kInvalidArguments; | |
| QuerySync* sync = static_cast<QuerySync*>( | |
| buffer->GetDataAddress(sync_shm_offset, sizeof(QuerySync))); | |
| if (!sync) | |
| return error::kOutOfBounds; | |
| QueryManager::Query* query = query_manager_->GetQuery(client_id); | |
| if (!query) { | |
| if (!query_manager_->IsValidQuery(client_id)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, | |
| "glQueryCounterEXT", | |
| "id not made by glGenQueriesEXT"); | |
| return error::kNoError; | |
| } | |
| query = | |
| query_manager_->CreateQuery(target, client_id, std::move(buffer), sync); | |
| } else { | |
| if (query->target() != target) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glQueryCounterEXT", | |
| "target does not match"); | |
| return error::kNoError; | |
| } else if (query->sync() != sync) { | |
| DLOG(ERROR) << "Shared memory used by query not the same as before"; | |
| return error::kInvalidArguments; | |
| } | |
| } | |
| query_manager_->QueryCounter(query, submit_count); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleSetDisjointValueSyncCHROMIUM( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::SetDisjointValueSyncCHROMIUM& c = | |
| *static_cast<const volatile gles2::cmds::SetDisjointValueSyncCHROMIUM*>( | |
| cmd_data); | |
| int32_t sync_shm_id = static_cast<int32_t>(c.sync_data_shm_id); | |
| uint32_t sync_shm_offset = static_cast<uint32_t>(c.sync_data_shm_offset); | |
| return query_manager_->SetDisjointSync(sync_shm_id, sync_shm_offset); | |
| } | |
| bool GLES2DecoderImpl::GenVertexArraysOESHelper( | |
| GLsizei n, const GLuint* client_ids) { | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| if (GetVertexAttribManager(UNSAFE_TODO(client_ids[ii]))) { | |
| return false; | |
| } | |
| } | |
| if (!features().native_vertex_array_object) { | |
| // Emulated VAO | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| CreateVertexAttribManager(UNSAFE_TODO(client_ids[ii]), 0, true); | |
| } | |
| } else { | |
| auto service_ids = base::HeapArray<GLuint>::Uninit(n); | |
| api()->glGenVertexArraysOESFn(n, service_ids.data()); | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| CreateVertexAttribManager(UNSAFE_TODO(client_ids[ii]), service_ids[ii], | |
| true); | |
| } | |
| } | |
| return true; | |
| } | |
| void GLES2DecoderImpl::DeleteVertexArraysOESHelper( | |
| GLsizei n, | |
| const volatile GLuint* client_ids) { | |
| for (GLsizei ii = 0; ii < n; ++ii) { | |
| GLuint client_id = UNSAFE_TODO(client_ids[ii]); | |
| VertexAttribManager* vao = GetVertexAttribManager(client_id); | |
| if (vao && !vao->IsDeleted()) { | |
| if (state_.vertex_attrib_manager.get() == vao) { | |
| DoBindVertexArrayOES(0); | |
| } | |
| RemoveVertexAttribManager(client_id); | |
| } | |
| } | |
| } | |
| void GLES2DecoderImpl::DoBindVertexArrayOES(GLuint client_id) { | |
| VertexAttribManager* vao = nullptr; | |
| if (client_id != 0) { | |
| vao = GetVertexAttribManager(client_id); | |
| if (!vao) { | |
| // Unlike most Bind* methods, the spec explicitly states that VertexArray | |
| // only allows names that have been previously generated. As such, we do | |
| // not generate new names here. | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, | |
| "glBindVertexArrayOES", "bad vertex array id."); | |
| current_decoder_error_ = error::kNoError; | |
| return; | |
| } | |
| } else { | |
| vao = state_.default_vertex_attrib_manager.get(); | |
| } | |
| // Only set the VAO state if it's changed | |
| if (state_.vertex_attrib_manager.get() != vao) { | |
| if (state_.vertex_attrib_manager) | |
| state_.vertex_attrib_manager->SetIsBound(false); | |
| state_.vertex_attrib_manager = vao; | |
| if (vao) | |
| vao->SetIsBound(true); | |
| if (!features().native_vertex_array_object) { | |
| EmulateVertexArrayState(); | |
| } else { | |
| GLuint service_id = vao->service_id(); | |
| api()->glBindVertexArrayOESFn(service_id); | |
| } | |
| } | |
| } | |
| // Used when OES_vertex_array_object isn't natively supported | |
| void GLES2DecoderImpl::EmulateVertexArrayState() { | |
| // Setup the Vertex attribute state | |
| for (uint32_t vv = 0; vv < group_->max_vertex_attribs(); ++vv) { | |
| RestoreStateForAttrib(vv, true); | |
| } | |
| // Setup the element buffer | |
| Buffer* element_array_buffer = | |
| state_.vertex_attrib_manager->element_array_buffer(); | |
| api()->glBindBufferFn( | |
| GL_ELEMENT_ARRAY_BUFFER, | |
| element_array_buffer ? element_array_buffer->service_id() : 0); | |
| } | |
| bool GLES2DecoderImpl::DoIsVertexArrayOES(GLuint client_id) { | |
| const VertexAttribManager* vao = | |
| GetVertexAttribManager(client_id); | |
| return vao && vao->IsValid() && !vao->IsDeleted(); | |
| } | |
| bool GLES2DecoderImpl::DoIsSync(GLuint client_id) { | |
| GLsync service_sync = 0; | |
| return group_->GetSyncServiceId(client_id, &service_sync); | |
| } | |
| bool GLES2DecoderImpl::ValidateCopyTextureCHROMIUMTextures( | |
| const char* function_name, | |
| GLenum dest_target, | |
| TextureRef* source_texture_ref, | |
| TextureRef* dest_texture_ref) { | |
| if (!source_texture_ref || !dest_texture_ref) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "unknown texture id"); | |
| return false; | |
| } | |
| Texture* source_texture = source_texture_ref->texture(); | |
| Texture* dest_texture = dest_texture_ref->texture(); | |
| if (source_texture == dest_texture) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "source and destination textures are the same"); | |
| return false; | |
| } | |
| if (dest_texture->target() != | |
| GLES2Util::GLFaceTargetToTextureTarget(dest_target)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "target should be aligned with dest target"); | |
| return false; | |
| } | |
| switch (dest_texture->target()) { | |
| case GL_TEXTURE_2D: | |
| case GL_TEXTURE_CUBE_MAP: | |
| case GL_TEXTURE_RECTANGLE_ANGLE: | |
| break; | |
| default: | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, | |
| "invalid dest texture target binding"); | |
| return false; | |
| } | |
| switch (source_texture->target()) { | |
| case GL_TEXTURE_2D: | |
| case GL_TEXTURE_RECTANGLE_ANGLE: | |
| case GL_TEXTURE_EXTERNAL_OES: | |
| break; | |
| default: | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, | |
| "invalid source texture target binding"); | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool GLES2DecoderImpl::CanUseCopyTextureCHROMIUMInternalFormat( | |
| GLenum dest_internal_format) { | |
| switch (dest_internal_format) { | |
| case GL_RGB: | |
| case GL_RGBA: | |
| case GL_RGB8: | |
| case GL_RGBA8: | |
| case GL_BGRA_EXT: | |
| case GL_BGRA8_EXT: | |
| case GL_SRGB_EXT: | |
| case GL_SRGB_ALPHA_EXT: | |
| case GL_R8: | |
| case GL_R8UI: | |
| case GL_RG8: | |
| case GL_RG8UI: | |
| case GL_SRGB8: | |
| case GL_RGB565: | |
| case GL_RGB8UI: | |
| case GL_SRGB8_ALPHA8: | |
| case GL_RGB5_A1: | |
| case GL_RGBA4: | |
| case GL_RGBA8UI: | |
| case GL_RGB9_E5: | |
| case GL_R16F: | |
| case GL_R32F: | |
| case GL_RG16F: | |
| case GL_RG32F: | |
| case GL_RGB16F: | |
| case GL_RGB32F: | |
| case GL_RGBA16F: | |
| case GL_RGBA32F: | |
| case GL_R11F_G11F_B10F: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| void GLES2DecoderImpl::DoCopyTextureCHROMIUM( | |
| GLuint source_id, | |
| GLint source_level, | |
| GLenum dest_target, | |
| GLuint dest_id, | |
| GLint dest_level, | |
| GLenum internal_format, | |
| GLenum dest_type, | |
| GLboolean unpack_flip_y, | |
| GLboolean unpack_premultiply_alpha, | |
| GLboolean unpack_unmultiply_alpha) { | |
| TRACE_EVENT0("gpu", "GLES2DecoderImpl::DoCopyTextureCHROMIUM"); | |
| static const char kFunctionName[] = "glCopyTextureCHROMIUM"; | |
| TextureRef* source_texture_ref = GetTexture(source_id); | |
| TextureRef* dest_texture_ref = GetTexture(dest_id); | |
| if (!ValidateCopyTextureCHROMIUMTextures( | |
| kFunctionName, dest_target, source_texture_ref, dest_texture_ref)) { | |
| return; | |
| } | |
| if (source_level < 0 || dest_level < 0 || | |
| (feature_info_->IsWebGL1OrES2Context() && source_level > 0)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, kFunctionName, | |
| "source_level or dest_level out of range"); | |
| return; | |
| } | |
| Texture* source_texture = source_texture_ref->texture(); | |
| Texture* dest_texture = dest_texture_ref->texture(); | |
| GLenum source_target = source_texture->target(); | |
| GLenum dest_binding_target = dest_texture->target(); | |
| GLenum source_type = 0; | |
| GLenum source_internal_format = 0; | |
| source_texture->GetLevelType(source_target, source_level, &source_type, | |
| &source_internal_format); | |
| GLenum format = | |
| TextureManager::ExtractFormatFromStorageFormat(internal_format); | |
| if (!texture_manager()->ValidateTextureParameters( | |
| error_state_.get(), kFunctionName, true, format, dest_type, | |
| internal_format, dest_level)) { | |
| return; | |
| } | |
| std::string output_error_msg; | |
| if (!ValidateCopyTextureCHROMIUMInternalFormats( | |
| GetFeatureInfo(), source_internal_format, internal_format, | |
| &output_error_msg)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, kFunctionName, | |
| output_error_msg.c_str()); | |
| return; | |
| } | |
| if (source_target == GL_TEXTURE_EXTERNAL_OES && | |
| CopyTextureCHROMIUMNeedsESSL3(internal_format) && | |
| !feature_info_->feature_flags().oes_egl_image_external_essl3) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, kFunctionName, | |
| "Copy*TextureCHROMIUM from EXTERNAL_OES to integer " | |
| "format requires OES_EGL_image_external_essl3"); | |
| return; | |
| } | |
| int source_width = 0; | |
| int source_height = 0; | |
| if (!source_texture->GetLevelSize(source_target, source_level, &source_width, | |
| &source_height, nullptr)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, kFunctionName, | |
| "source texture has no data for level"); | |
| return; | |
| } | |
| // Check that this type of texture is allowed. | |
| if (!texture_manager()->ValidForTextureTarget( | |
| source_texture, source_level, source_width, source_height, 1)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, kFunctionName, "Bad dimensions"); | |
| return; | |
| } | |
| if (dest_texture->IsImmutable()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, kFunctionName, | |
| "texture is immutable"); | |
| return; | |
| } | |
| // Clear the source texture if necessary. | |
| if (!texture_manager()->ClearTextureLevel(this, source_texture_ref, | |
| source_target, source_level)) { | |
| LOCAL_SET_GL_ERROR(GL_OUT_OF_MEMORY, kFunctionName, "dimensions too big"); | |
| return; | |
| } | |
| if (!InitializeCopyTextureCHROMIUM(kFunctionName)) | |
| return; | |
| GLenum dest_type_previous = dest_type; | |
| GLenum dest_internal_format = internal_format; | |
| int dest_width = 0; | |
| int dest_height = 0; | |
| bool dest_level_defined = dest_texture->GetLevelSize( | |
| dest_target, dest_level, &dest_width, &dest_height, nullptr); | |
| if (dest_level_defined) { | |
| dest_texture->GetLevelType(dest_target, dest_level, &dest_type_previous, | |
| &dest_internal_format); | |
| } | |
| // Resize the destination texture to the dimensions of the source texture. | |
| if (!dest_level_defined || dest_width != source_width || | |
| dest_height != source_height || | |
| dest_internal_format != internal_format || | |
| dest_type_previous != dest_type) { | |
| // Ensure that the glTexImage2D succeeds. | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER(kFunctionName); | |
| api()->glBindTextureFn(dest_binding_target, dest_texture->service_id()); | |
| ScopedPixelUnpackState reset_restore(&state_); | |
| api()->glTexImage2DFn( | |
| dest_target, dest_level, | |
| TextureManager::AdjustTexInternalFormat(feature_info_.get(), | |
| internal_format, dest_type), | |
| source_width, source_height, 0, | |
| TextureManager::AdjustTexFormat(feature_info_.get(), format), dest_type, | |
| nullptr); | |
| GLenum error = LOCAL_PEEK_GL_ERROR(kFunctionName); | |
| if (error != GL_NO_ERROR) { | |
| RestoreCurrentTextureBindings(&state_, dest_binding_target, | |
| state_.active_texture_unit); | |
| return; | |
| } | |
| texture_manager()->SetLevelInfo( | |
| dest_texture_ref, dest_target, dest_level, internal_format, | |
| source_width, source_height, 1, 0, format, dest_type, gfx::Rect()); | |
| dest_texture->ApplyFormatWorkarounds(feature_info_.get()); | |
| } else { | |
| texture_manager()->SetLevelCleared(dest_texture_ref, dest_target, | |
| dest_level, false); | |
| } | |
| CopyTextureMethod method = GetCopyTextureCHROMIUMMethod( | |
| GetFeatureInfo(), source_target, source_level, source_internal_format, | |
| source_type, dest_binding_target, dest_level, internal_format, | |
| unpack_flip_y == GL_TRUE, unpack_premultiply_alpha == GL_TRUE, | |
| unpack_unmultiply_alpha == GL_TRUE); | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER(kFunctionName); | |
| copy_texture_chromium_->DoCopyTexture( | |
| this, source_target, source_texture->service_id(), source_level, | |
| source_internal_format, dest_target, dest_texture->service_id(), | |
| dest_level, internal_format, source_width, source_height, | |
| unpack_flip_y == GL_TRUE, unpack_premultiply_alpha == GL_TRUE, | |
| unpack_unmultiply_alpha == GL_TRUE, method, copy_tex_image_blit_.get()); | |
| if (LOCAL_PEEK_GL_ERROR(kFunctionName) == GL_NO_ERROR) { | |
| texture_manager()->SetLevelCleared(dest_texture_ref, dest_target, | |
| dest_level, true); | |
| } | |
| } | |
| void GLES2DecoderImpl::CopySubTextureHelper(const char* function_name, | |
| GLuint source_id, | |
| GLint source_level, | |
| GLenum dest_target, | |
| GLuint dest_id, | |
| GLint dest_level, | |
| GLint xoffset, | |
| GLint yoffset, | |
| GLint x, | |
| GLint y, | |
| GLsizei width, | |
| GLsizei height, | |
| GLboolean unpack_flip_y, | |
| GLboolean unpack_premultiply_alpha, | |
| GLboolean unpack_unmultiply_alpha) { | |
| TextureRef* source_texture_ref = GetTexture(source_id); | |
| TextureRef* dest_texture_ref = GetTexture(dest_id); | |
| if (!ValidateCopyTextureCHROMIUMTextures( | |
| function_name, dest_target, source_texture_ref, dest_texture_ref)) { | |
| return; | |
| } | |
| if (source_level < 0 || dest_level < 0 || | |
| (feature_info_->IsWebGL1OrES2Context() && source_level > 0)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, | |
| "source_level or dest_level out of range"); | |
| return; | |
| } | |
| Texture* source_texture = source_texture_ref->texture(); | |
| Texture* dest_texture = dest_texture_ref->texture(); | |
| GLenum source_target = source_texture->target(); | |
| GLenum dest_binding_target = dest_texture->target(); | |
| int source_width = 0; | |
| int source_height = 0; | |
| if (!source_texture->GetLevelSize(source_target, source_level, &source_width, | |
| &source_height, nullptr)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, | |
| "source texture has no data for level"); | |
| return; | |
| } | |
| // Check that this type of texture is allowed. | |
| if (!texture_manager()->ValidForTextureTarget( | |
| source_texture, source_level, source_width, source_height, 1)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, | |
| "source texture bad dimensions"); | |
| return; | |
| } | |
| if (!source_texture->ValidForTexture(source_target, source_level, x, y, 0, | |
| width, height, 1)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, | |
| "source texture bad dimensions."); | |
| return; | |
| } | |
| GLenum source_type = 0; | |
| GLenum source_internal_format = 0; | |
| source_texture->GetLevelType(source_target, source_level, &source_type, | |
| &source_internal_format); | |
| GLenum dest_type = 0; | |
| GLenum dest_internal_format = 0; | |
| bool dest_level_defined = dest_texture->GetLevelType( | |
| dest_target, dest_level, &dest_type, &dest_internal_format); | |
| if (!dest_level_defined) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "destination texture is not defined"); | |
| return; | |
| } | |
| if (!dest_texture->ValidForTexture(dest_target, dest_level, xoffset, yoffset, | |
| 0, width, height, 1)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, | |
| "destination texture bad dimensions."); | |
| return; | |
| } | |
| std::string output_error_msg; | |
| if (!ValidateCopyTextureCHROMIUMInternalFormats( | |
| GetFeatureInfo(), source_internal_format, dest_internal_format, | |
| &output_error_msg)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| output_error_msg.c_str()); | |
| return; | |
| } | |
| if (source_target == GL_TEXTURE_EXTERNAL_OES && | |
| CopyTextureCHROMIUMNeedsESSL3(dest_internal_format) && | |
| !feature_info_->feature_flags().oes_egl_image_external_essl3) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "Copy*TextureCHROMIUM from EXTERNAL_OES to integer " | |
| "format requires OES_EGL_image_external_essl3"); | |
| return; | |
| } | |
| // Clear the source texture if necessary. | |
| if (!texture_manager()->ClearTextureLevel(this, source_texture_ref, | |
| source_target, source_level)) { | |
| LOCAL_SET_GL_ERROR(GL_OUT_OF_MEMORY, function_name, | |
| "source texture dimensions too big"); | |
| return; | |
| } | |
| if (!InitializeCopyTextureCHROMIUM(function_name)) | |
| return; | |
| int dest_width = 0; | |
| int dest_height = 0; | |
| bool ok = dest_texture->GetLevelSize(dest_target, dest_level, &dest_width, | |
| &dest_height, nullptr); | |
| DCHECK(ok); | |
| bool should_set_level_cleared_rect = false; | |
| gfx::Rect pending_cleared_rect; | |
| bool should_set_level_cleared = false; | |
| if (xoffset != 0 || yoffset != 0 || width != dest_width || | |
| height != dest_height) { | |
| gfx::Rect cleared_rect; | |
| if (TextureManager::CombineAdjacentRects( | |
| dest_texture->GetLevelClearedRect(dest_target, dest_level), | |
| gfx::Rect(xoffset, yoffset, width, height), &cleared_rect)) { | |
| DCHECK_GE(cleared_rect.size().GetArea(), | |
| dest_texture->GetLevelClearedRect(dest_target, dest_level) | |
| .size() | |
| .GetArea()); | |
| should_set_level_cleared_rect = true; | |
| pending_cleared_rect = cleared_rect; | |
| } else { | |
| // Otherwise clear part of texture level that is not already cleared. | |
| if (!texture_manager()->ClearTextureLevel(this, dest_texture_ref, | |
| dest_target, dest_level)) { | |
| LOCAL_SET_GL_ERROR(GL_OUT_OF_MEMORY, function_name, | |
| "destination texture dimensions too big"); | |
| return; | |
| } | |
| } | |
| } else { | |
| texture_manager()->SetLevelCleared(dest_texture_ref, dest_target, | |
| dest_level, false); | |
| should_set_level_cleared = true; | |
| } | |
| CopyTextureMethod method = GetCopyTextureCHROMIUMMethod( | |
| GetFeatureInfo(), source_target, source_level, source_internal_format, | |
| source_type, dest_binding_target, dest_level, dest_internal_format, | |
| unpack_flip_y == GL_TRUE, unpack_premultiply_alpha == GL_TRUE, | |
| unpack_unmultiply_alpha == GL_TRUE); | |
| // Use DRAW instead of COPY if the workaround is enabled. | |
| if (method == CopyTextureMethod::DIRECT_COPY && | |
| workarounds().prefer_draw_to_copy && | |
| (dest_level == 0 || feature_info_->IsWebGL2OrES3Context())) { | |
| method = CopyTextureMethod::DIRECT_DRAW; | |
| } | |
| if (should_set_level_cleared || should_set_level_cleared_rect) { | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER(function_name); | |
| } | |
| copy_texture_chromium_->DoCopySubTexture( | |
| this, source_target, source_texture->service_id(), source_level, | |
| source_internal_format, dest_target, dest_texture->service_id(), | |
| dest_level, dest_internal_format, xoffset, yoffset, x, y, width, height, | |
| dest_width, dest_height, source_width, source_height, | |
| unpack_flip_y == GL_TRUE, unpack_premultiply_alpha == GL_TRUE, | |
| unpack_unmultiply_alpha == GL_TRUE, method, copy_tex_image_blit_.get()); | |
| if (should_set_level_cleared || should_set_level_cleared_rect) { | |
| if (LOCAL_PEEK_GL_ERROR(function_name) == GL_NO_ERROR) { | |
| if (should_set_level_cleared) { | |
| texture_manager()->SetLevelCleared(dest_texture_ref, dest_target, | |
| dest_level, true); | |
| } else if (should_set_level_cleared_rect) { | |
| texture_manager()->SetLevelClearedRect( | |
| dest_texture_ref, dest_target, dest_level, pending_cleared_rect); | |
| } | |
| } | |
| } | |
| } | |
| void GLES2DecoderImpl::DoCopySubTextureCHROMIUM( | |
| GLuint source_id, | |
| GLint source_level, | |
| GLenum dest_target, | |
| GLuint dest_id, | |
| GLint dest_level, | |
| GLint xoffset, | |
| GLint yoffset, | |
| GLint x, | |
| GLint y, | |
| GLsizei width, | |
| GLsizei height, | |
| GLboolean unpack_flip_y, | |
| GLboolean unpack_premultiply_alpha, | |
| GLboolean unpack_unmultiply_alpha) { | |
| TRACE_EVENT0("gpu", "GLES2DecoderImpl::DoCopySubTextureCHROMIUM"); | |
| static const char kFunctionName[] = "glCopySubTextureCHROMIUM"; | |
| CopySubTextureHelper(kFunctionName, source_id, source_level, dest_target, | |
| dest_id, dest_level, xoffset, yoffset, x, y, width, | |
| height, unpack_flip_y, unpack_premultiply_alpha, | |
| unpack_unmultiply_alpha); | |
| } | |
| bool GLES2DecoderImpl::InitializeCopyTexImageBlitter( | |
| const char* function_name) { | |
| if (!copy_tex_image_blit_.get()) { | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER(function_name); | |
| copy_tex_image_blit_ = | |
| std::make_unique<CopyTexImageResourceManager>(feature_info_.get()); | |
| copy_tex_image_blit_->Initialize(this); | |
| if (LOCAL_PEEK_GL_ERROR(function_name) != GL_NO_ERROR) | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool GLES2DecoderImpl::InitializeCopyTextureCHROMIUM( | |
| const char* function_name) { | |
| // Defer initializing the CopyTextureCHROMIUMResourceManager until it is | |
| // needed because it takes 10s of milliseconds to initialize. | |
| if (!copy_texture_chromium_.get()) { | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER(function_name); | |
| copy_texture_chromium_.reset(CopyTextureCHROMIUMResourceManager::Create()); | |
| copy_texture_chromium_->Initialize(this, features()); | |
| if (LOCAL_PEEK_GL_ERROR(function_name) != GL_NO_ERROR) | |
| return false; | |
| // On the desktop core profile this also needs emulation of | |
| // CopyTex{Sub}Image2D for luminance, alpha, and luminance_alpha | |
| // textures. | |
| if (CopyTexImageResourceManager::CopyTexImageRequiresBlit( | |
| feature_info_.get(), GL_LUMINANCE)) { | |
| if (!InitializeCopyTexImageBlitter(function_name)) | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| void GLES2DecoderImpl::TexStorageImpl(GLenum target, | |
| GLsizei levels, | |
| GLenum internal_format, | |
| GLsizei width, | |
| GLsizei height, | |
| GLsizei depth, | |
| ContextState::Dimension dimension, | |
| const char* function_name) { | |
| if (levels == 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "levels == 0"); | |
| return; | |
| } | |
| bool is_compressed_format = IsCompressedTextureFormat(internal_format); | |
| if (is_compressed_format) { | |
| if (target == GL_TEXTURE_3D && | |
| !feature_info_->feature_flags().ext_texture_format_astc_hdr && | |
| ::gpu::gles2::IsASTCFormat(internal_format)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "target invalid for format"); | |
| return; | |
| } | |
| if (!::gpu::gles2::ValidateCompressedFormatTarget(target, | |
| internal_format)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "target invalid for format"); | |
| return; | |
| } | |
| } | |
| TextureRef* texture_ref = | |
| texture_manager()->GetTextureInfoForTarget(&state_, target); | |
| if (!texture_ref) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, | |
| "unknown texture for target"); | |
| return; | |
| } | |
| ScopedBufferReattacher reattacher(this, texture_ref); | |
| Texture* texture = texture_ref->texture(); | |
| // The glTexStorage entry points require width, height, and depth to be | |
| // at least 1, but the other texture entry points (those which use | |
| // ValidForTextureTarget) do not. So we have to add an extra check here. | |
| bool is_invalid_texstorage_size = width < 1 || height < 1 || depth < 1; | |
| if (!texture_manager()->ValidForTextureTarget(texture, 0, width, height, | |
| depth) || | |
| is_invalid_texstorage_size) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, function_name, "dimensions out of range"); | |
| return; | |
| } | |
| // glTexStorage generates GL_INVALID_OPERATION for out of bounds level | |
| // which is a bit different from other GL calls generating GL_INVALID_VALUE | |
| if (TextureManager::ComputeMipMapCount(target, width, height, depth) < | |
| levels) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, function_name, "too many levels"); | |
| return; | |
| } | |
| if (texture->IsAttachedToFramebuffer()) { | |
| framebuffer_state_.clear_state_dirty = true; | |
| } | |
| if (texture->IsImmutable()) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, function_name, "texture is immutable"); | |
| return; | |
| } | |
| GLenum format = TextureManager::ExtractFormatFromStorageFormat( | |
| internal_format); | |
| GLenum type = TextureManager::ExtractTypeFromStorageFormat(internal_format); | |
| { | |
| GLsizei level_width = width; | |
| GLsizei level_height = height; | |
| GLsizei level_depth = depth; | |
| base::CheckedNumeric<uint32_t> estimated_size(0); | |
| PixelStoreParams params; | |
| params.alignment = 1; | |
| for (int ii = 0; ii < levels; ++ii) { | |
| GLsizei pessimized_level_width = level_width; | |
| GLsizei pessimized_level_height = level_height; | |
| GLsizei pessimized_level_depth = level_depth; | |
| if (workarounds().round_up_3d_texture_size_to_pot_for_limit && | |
| (target == GL_TEXTURE_3D || depth > 1)) { | |
| pessimized_level_width = static_cast<GLsizei>( | |
| std::bit_ceil(static_cast<uint32_t>(level_width))); | |
| pessimized_level_height = static_cast<GLsizei>( | |
| std::bit_ceil(static_cast<uint32_t>(level_height))); | |
| pessimized_level_depth = static_cast<GLsizei>( | |
| std::bit_ceil(static_cast<uint32_t>(level_depth))); | |
| } | |
| uint32_t size; | |
| if (is_compressed_format) { | |
| GLsizei level_size; | |
| if (!GetCompressedTexSizeInBytes( | |
| function_name, pessimized_level_width, pessimized_level_height, | |
| pessimized_level_depth, internal_format, &level_size, | |
| error_state_.get())) { | |
| // GetCompressedTexSizeInBytes() already generated a GL error | |
| // (e.g. in the case of overflow, GL_INVALID_VALUE). | |
| return; | |
| } | |
| size = static_cast<uint32_t>(level_size); | |
| } else { | |
| if (!GLES2Util::ComputeImageDataSizesES3( | |
| pessimized_level_width, pessimized_level_height, | |
| pessimized_level_depth, format, type, params, &size, nullptr, | |
| nullptr, nullptr, nullptr)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_OUT_OF_MEMORY, function_name, "dimensions too large"); | |
| return; | |
| } | |
| } | |
| estimated_size += size; | |
| level_width = std::max(1, level_width >> 1); | |
| level_height = std::max(1, level_height >> 1); | |
| if (target == GL_TEXTURE_3D) | |
| level_depth = std::max(1, level_depth >> 1); | |
| } | |
| if (!estimated_size.IsValid()) { | |
| LOCAL_SET_GL_ERROR(GL_OUT_OF_MEMORY, function_name, "out of memory"); | |
| return; | |
| } | |
| } | |
| // First lookup compatibility format via texture manager for swizzling legacy | |
| // LUMINANCE/ALPHA formats. | |
| GLenum compatibility_internal_format = | |
| texture_manager()->AdjustTexStorageFormat(feature_info_.get(), | |
| internal_format); | |
| // Then lookup compatibility format for compressed formats. | |
| const CompressedFormatInfo* format_info = | |
| GetCompressedFormatInfo(internal_format); | |
| if (format_info != nullptr && !format_info->support_check(*feature_info_)) { | |
| compatibility_internal_format = format_info->decompressed_internal_format; | |
| } | |
| // TODO(zmo): We might need to emulate TexStorage using TexImage or | |
| // CompressedTexImage on Mac OSX where we expose ES3 APIs when the underlying | |
| // driver is lower than 4.2 and ARB_texture_storage extension doesn't exist. | |
| bool reset_base_level = | |
| workarounds().reset_tex_storage_base_level && texture->base_level() != 0; | |
| if (reset_base_level) { | |
| api()->glTexParameteriFn(target, GL_TEXTURE_BASE_LEVEL, 0); | |
| } | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER(function_name); | |
| if (dimension == ContextState::k2D) { | |
| api()->glTexStorage2DEXTFn(target, levels, compatibility_internal_format, | |
| width, height); | |
| } else { | |
| api()->glTexStorage3DFn(target, levels, compatibility_internal_format, | |
| width, height, depth); | |
| } | |
| if (reset_base_level) { | |
| api()->glTexParameteriFn(target, GL_TEXTURE_BASE_LEVEL, | |
| texture->base_level()); | |
| } | |
| GLenum error = LOCAL_PEEK_GL_ERROR(function_name); | |
| if (error != GL_NO_ERROR) { | |
| // The driver rejected the allocation. Do NOT update the decoder-side | |
| // LevelInfo / immutable flag, otherwise subsequent TexSubImage bounds | |
| // checks (Texture::ValidForTexture) would validate against dimensions | |
| // that the driver never allocated, allowing oversized writes to be | |
| // forwarded to the native driver. | |
| return; | |
| } | |
| { | |
| GLsizei level_width = width; | |
| GLsizei level_height = height; | |
| GLsizei level_depth = depth; | |
| GLenum adjusted_internal_format = | |
| feature_info_->IsWebGL1OrES2Context() ? format : internal_format; | |
| for (int ii = 0; ii < levels; ++ii) { | |
| if (target == GL_TEXTURE_CUBE_MAP) { | |
| for (int jj = 0; jj < 6; ++jj) { | |
| GLenum face = GL_TEXTURE_CUBE_MAP_POSITIVE_X + jj; | |
| texture_manager()->SetLevelInfo( | |
| texture_ref, face, ii, adjusted_internal_format, level_width, | |
| level_height, 1, 0, format, type, gfx::Rect()); | |
| } | |
| } else { | |
| texture_manager()->SetLevelInfo( | |
| texture_ref, target, ii, adjusted_internal_format, level_width, | |
| level_height, level_depth, 0, format, type, gfx::Rect()); | |
| } | |
| level_width = std::max(1, level_width >> 1); | |
| level_height = std::max(1, level_height >> 1); | |
| if (target == GL_TEXTURE_3D) | |
| level_depth = std::max(1, level_depth >> 1); | |
| } | |
| texture->ApplyFormatWorkarounds(feature_info_.get()); | |
| texture->SetImmutable(true, true); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoTexStorage2DEXT(GLenum target, | |
| GLsizei levels, | |
| GLenum internal_format, | |
| GLsizei width, | |
| GLsizei height) { | |
| TRACE_EVENT2("gpu", "GLES2DecoderImpl::DoTexStorage2D", | |
| "width", width, "height", height); | |
| TexStorageImpl(target, levels, internal_format, width, height, 1, | |
| ContextState::k2D, "glTexStorage2D"); | |
| } | |
| void GLES2DecoderImpl::DoTexStorage3D(GLenum target, | |
| GLsizei levels, | |
| GLenum internal_format, | |
| GLsizei width, | |
| GLsizei height, | |
| GLsizei depth) { | |
| TRACE_EVENT2("gpu", "GLES2DecoderImpl::DoTexStorage3D", | |
| "widthXheight", width * height, "depth", depth); | |
| TexStorageImpl(target, levels, internal_format, width, height, depth, | |
| ContextState::k3D, "glTexStorage3D"); | |
| } | |
| void GLES2DecoderImpl::DoCreateAndTexStorage2DSharedImageINTERNAL( | |
| GLuint client_id, | |
| const volatile GLbyte* mailbox_data) { | |
| TRACE_EVENT2("gpu", | |
| "GLES2DecoderImpl::DoCreateAndTexStorage2DSharedImageCHROMIUM", | |
| "context", logger_.GetLogPrefix(), "mailbox[0]", | |
| static_cast<unsigned char>(mailbox_data[0])); | |
| Mailbox mailbox = Mailbox::FromVolatile( | |
| *reinterpret_cast<const volatile Mailbox*>(mailbox_data)); | |
| DLOG_IF(ERROR, !mailbox.Verify()) | |
| << "DoCreateAndTexStorage2DSharedImageCHROMIUM was passed an invalid " | |
| "mailbox."; | |
| if (!client_id) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, | |
| "DoCreateAndTexStorage2DSharedImageINTERNAL", | |
| "invalid client id"); | |
| return; | |
| } | |
| TextureRef* texture_ref = GetTexture(client_id); | |
| if (texture_ref) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, | |
| "DoCreateAndTexStorage2DSharedImageINTERNAL", | |
| "client id already in use"); | |
| return; | |
| } | |
| std::unique_ptr<GLTextureImageRepresentation> shared_image = | |
| group_->shared_image_representation_factory()->ProduceGLTexture(mailbox); | |
| if (!shared_image) { | |
| // Mailbox missing, generate a texture. | |
| bool result = GenTexturesHelper(1, &client_id); | |
| DCHECK(result); | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, | |
| "DoCreateAndTexStorage2DSharedImageINTERNAL", | |
| "invalid mailbox name"); | |
| return; | |
| } | |
| texture_ref = | |
| texture_manager()->ConsumeSharedImage(client_id, std::move(shared_image)); | |
| } | |
| void GLES2DecoderImpl::DoBeginSharedImageAccessDirectCHROMIUM(GLuint client_id, | |
| GLenum mode) { | |
| TextureRef* texture_ref = GetTexture(client_id); | |
| if (!texture_ref) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "DoBeginSharedImageAccessCHROMIUM", | |
| "invalid texture id"); | |
| return; | |
| } | |
| GLTextureImageRepresentation* shared_image = texture_ref->shared_image(); | |
| if (!shared_image) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "DoBeginSharedImageAccessCHROMIUM", | |
| "bound texture is not a shared image"); | |
| return; | |
| } | |
| if (texture_ref->shared_image_scoped_access()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "DoBeginSharedImageAccessCHROMIUM", | |
| "shared image is being accessed"); | |
| return; | |
| } | |
| if (!texture_ref->BeginAccessSharedImage(mode)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "DoBeginSharedImageAccessCHROMIUM", | |
| "Unable to begin access"); | |
| return; | |
| } | |
| } | |
| void GLES2DecoderImpl::DoEndSharedImageAccessDirectCHROMIUM(GLuint client_id) { | |
| TextureRef* texture_ref = GetTexture(client_id); | |
| if (!texture_ref) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "DoBeginSharedImageAccessCHROMIUM", | |
| "invalid texture id"); | |
| return; | |
| } | |
| GLTextureImageRepresentation* shared_image = texture_ref->shared_image(); | |
| if (!shared_image) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "DoEndSharedImageAccessCHROMIUM", | |
| "bound texture is not a shared image"); | |
| return; | |
| } | |
| if (!texture_ref->shared_image_scoped_access()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "DoEndSharedImageAccessCHROMIUM", | |
| "shared image is not being accessed"); | |
| return; | |
| } | |
| texture_ref->EndAccessSharedImage(); | |
| } | |
| void GLES2DecoderImpl::DoCopySharedImageINTERNAL( | |
| GLint xoffset, | |
| GLint yoffset, | |
| GLint x, | |
| GLint y, | |
| GLsizei width, | |
| GLsizei height, | |
| const volatile GLbyte* mailboxes) { | |
| NOTIMPLEMENTED_LOG_ONCE(); | |
| } | |
| void GLES2DecoderImpl::DoCopySharedImageToTextureINTERNAL( | |
| GLuint texture, | |
| GLenum target, | |
| GLuint internal_format, | |
| GLenum type, | |
| GLint src_x, | |
| GLint src_y, | |
| GLsizei width, | |
| GLsizei height, | |
| GLboolean is_dst_origin_top_left, | |
| const volatile GLbyte* src_mailbox) { | |
| NOTIMPLEMENTED_LOG_ONCE(); | |
| } | |
| void GLES2DecoderImpl::DoInsertEventMarkerEXT( | |
| GLsizei length, const GLchar* marker) { | |
| if (!marker) { | |
| marker = ""; | |
| } | |
| debug_marker_manager_.SetMarker( | |
| length ? std::string(marker, length) : std::string(marker)); | |
| } | |
| void GLES2DecoderImpl::DoPushGroupMarkerEXT( | |
| GLsizei /*length*/, const GLchar* /*marker*/) { | |
| } | |
| void GLES2DecoderImpl::DoPopGroupMarkerEXT(void) { | |
| } | |
| error::Error GLES2DecoderImpl::HandleTraceBeginCHROMIUM( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::TraceBeginCHROMIUM& c = | |
| *static_cast<const volatile gles2::cmds::TraceBeginCHROMIUM*>(cmd_data); | |
| Bucket* category_bucket = GetBucket(c.category_bucket_id); | |
| Bucket* name_bucket = GetBucket(c.name_bucket_id); | |
| static constexpr size_t kMaxStrLen = 256; | |
| if (!category_bucket || category_bucket->size() == 0 || | |
| category_bucket->size() > kMaxStrLen || !name_bucket || | |
| name_bucket->size() == 0 || name_bucket->size() > kMaxStrLen) { | |
| return error::kInvalidArguments; | |
| } | |
| std::string category_name; | |
| std::string trace_name; | |
| if (!category_bucket->GetAsString(&category_name) || | |
| !name_bucket->GetAsString(&trace_name)) { | |
| return error::kInvalidArguments; | |
| } | |
| debug_marker_manager_.PushGroup(trace_name); | |
| if (!gpu_tracer_->Begin(category_name, trace_name, kTraceCHROMIUM)) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, | |
| "glTraceBeginCHROMIUM", "unable to create begin trace"); | |
| return error::kNoError; | |
| } | |
| return error::kNoError; | |
| } | |
| void GLES2DecoderImpl::DoTraceEndCHROMIUM() { | |
| debug_marker_manager_.PopGroup(); | |
| if (!gpu_tracer_->End(kTraceCHROMIUM)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, | |
| "glTraceEndCHROMIUM", "no trace begin found"); | |
| return; | |
| } | |
| } | |
| void GLES2DecoderImpl::DoDrawBuffersEXT(GLsizei count, | |
| const volatile GLenum* bufs) { | |
| DCHECK_LE(group_->max_draw_buffers(), 16u); | |
| if (count > static_cast<GLsizei>(group_->max_draw_buffers())) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_VALUE, | |
| "glDrawBuffersEXT", "greater than GL_MAX_DRAW_BUFFERS_EXT"); | |
| return; | |
| } | |
| Framebuffer* framebuffer = GetFramebufferInfoForTarget(GL_FRAMEBUFFER); | |
| if (framebuffer) { | |
| std::array<GLenum, 16> safe_bufs; | |
| for (GLsizei i = 0; i < count; ++i) { | |
| GLenum buf = UNSAFE_TODO(bufs[i]); | |
| if (buf != static_cast<GLenum>(GL_COLOR_ATTACHMENT0 + i) && | |
| buf != GL_NONE) { | |
| LOCAL_SET_GL_ERROR( | |
| GL_INVALID_OPERATION, | |
| "glDrawBuffersEXT", | |
| "bufs[i] not GL_NONE or GL_COLOR_ATTACHMENTi_EXT"); | |
| return; | |
| } | |
| safe_bufs[i] = buf; | |
| } | |
| api()->glDrawBuffersARBFn(count, safe_bufs.data()); | |
| framebuffer->SetDrawBuffers( | |
| base::span(safe_bufs).first(static_cast<size_t>(count))); | |
| } else { // backbuffer | |
| if (count != 1) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glDrawBuffersEXT", | |
| "invalid number of buffers"); | |
| return; | |
| } | |
| GLenum buf = bufs[0]; | |
| if (buf != GL_BACK && buf != GL_NONE) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glDrawBuffersEXT", | |
| "buffer is not GL_NONE or GL_BACK"); | |
| return; | |
| } | |
| back_buffer_draw_buffer_ = buf; | |
| if (buf == GL_BACK && GetBackbufferServiceId() != 0) // emulated backbuffer | |
| buf = GL_COLOR_ATTACHMENT0; | |
| api()->glDrawBuffersARBFn(count, &buf); | |
| } | |
| } | |
| void GLES2DecoderImpl::DoLoseContextCHROMIUM(GLenum current, GLenum other) { | |
| MarkContextLost(GetContextLostReasonFromResetStatus(current)); | |
| group_->LoseContexts(GetContextLostReasonFromResetStatus(other)); | |
| reset_by_robustness_extension_ = true; | |
| } | |
| void GLES2DecoderImpl::DoFlushDriverCachesCHROMIUM(void) { | |
| // On Adreno Android devices we need to use a workaround to force caches to | |
| // clear. | |
| if (workarounds().unbind_egl_context_to_flush_driver_caches) { | |
| context_->ReleaseCurrent(nullptr); | |
| context_->MakeCurrent(surface_.get()); | |
| } | |
| } | |
| error::Error GLES2DecoderImpl::HandleUniformBlockBinding( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const char* func_name = "glUniformBlockBinding"; | |
| if (!feature_info_->IsWebGL2OrES3Context()) | |
| return error::kUnknownCommand; | |
| const volatile gles2::cmds::UniformBlockBinding& c = | |
| *static_cast<const volatile gles2::cmds::UniformBlockBinding*>(cmd_data); | |
| GLuint client_id = c.program; | |
| GLuint index = static_cast<GLuint>(c.index); | |
| GLuint binding = static_cast<GLuint>(c.binding); | |
| Program* program = GetProgramInfoNotShader(client_id, func_name); | |
| if (!program) { | |
| return error::kNoError; | |
| } | |
| if (index >= program->uniform_block_size_info().size()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name, | |
| "uniformBlockIndex is not an active uniform block index"); | |
| return error::kNoError; | |
| } | |
| if (binding >= group_->max_uniform_buffer_bindings()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, func_name, | |
| "uniformBlockBinding >= MAX_UNIFORM_BUFFER_BINDINGS"); | |
| return error::kNoError; | |
| } | |
| GLuint service_id = program->service_id(); | |
| api()->glUniformBlockBindingFn(service_id, index, binding); | |
| program->SetUniformBlockBinding(index, binding); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleClientWaitSync( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const char* function_name = "glClientWaitSync"; | |
| if (!feature_info_->IsWebGL2OrES3Context()) | |
| return error::kUnknownCommand; | |
| const volatile gles2::cmds::ClientWaitSync& c = | |
| *static_cast<const volatile gles2::cmds::ClientWaitSync*>(cmd_data); | |
| const GLuint sync = static_cast<GLuint>(c.sync); | |
| GLbitfield flags = static_cast<GLbitfield>(c.flags); | |
| const GLuint64 timeout = c.timeout(); | |
| typedef cmds::ClientWaitSync::Result Result; | |
| Result* result_dst = GetSharedMemoryAs<Result*>( | |
| c.result_shm_id, c.result_shm_offset, sizeof(*result_dst)); | |
| if (!result_dst) { | |
| return error::kOutOfBounds; | |
| } | |
| if (*result_dst != GL_WAIT_FAILED) { | |
| return error::kInvalidArguments; | |
| } | |
| GLsync service_sync = 0; | |
| if (!group_->GetSyncServiceId(sync, &service_sync)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "invalid sync"); | |
| return error::kNoError; | |
| } | |
| if ((flags & ~GL_SYNC_FLUSH_COMMANDS_BIT) != 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "invalid flags"); | |
| return error::kNoError; | |
| } | |
| // Force GL_SYNC_FLUSH_COMMANDS_BIT to avoid infinite wait. | |
| flags |= GL_SYNC_FLUSH_COMMANDS_BIT; | |
| GLenum status = api()->glClientWaitSyncFn(service_sync, flags, timeout); | |
| switch (status) { | |
| case GL_ALREADY_SIGNALED: | |
| case GL_TIMEOUT_EXPIRED: | |
| case GL_CONDITION_SATISFIED: | |
| break; | |
| case GL_WAIT_FAILED: | |
| // Avoid leaking GL errors when using virtual contexts. | |
| LOCAL_PEEK_GL_ERROR(function_name); | |
| *result_dst = status; | |
| // If validation is complete, this only happens if the context is lost. | |
| return error::kLostContext; | |
| default: | |
| NOTREACHED(); | |
| } | |
| *result_dst = status; | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleWaitSync(uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const char* function_name = "glWaitSync"; | |
| if (!feature_info_->IsWebGL2OrES3Context()) | |
| return error::kUnknownCommand; | |
| const volatile gles2::cmds::WaitSync& c = | |
| *static_cast<const volatile gles2::cmds::WaitSync*>(cmd_data); | |
| const GLuint sync = static_cast<GLuint>(c.sync); | |
| const GLbitfield flags = static_cast<GLbitfield>(c.flags); | |
| const GLuint64 timeout = c.timeout(); | |
| GLsync service_sync = 0; | |
| if (!group_->GetSyncServiceId(sync, &service_sync)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "invalid sync"); | |
| return error::kNoError; | |
| } | |
| if (flags != 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "invalid flags"); | |
| return error::kNoError; | |
| } | |
| if (timeout != GL_TIMEOUT_IGNORED) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "invalid timeout"); | |
| return error::kNoError; | |
| } | |
| api()->glWaitSyncFn(service_sync, flags, timeout); | |
| return error::kNoError; | |
| } | |
| GLsync GLES2DecoderImpl::DoFenceSync(GLenum condition, GLbitfield flags) { | |
| const char* function_name = "glFenceSync"; | |
| if (condition != GL_SYNC_GPU_COMMANDS_COMPLETE) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_ENUM, function_name, "invalid condition"); | |
| return 0; | |
| } | |
| if (flags != 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, function_name, "invalid flags"); | |
| return 0; | |
| } | |
| GLsync sync = api()->glFenceSyncFn(condition, flags); | |
| ApplyRecreateFboWorkaroundIfNeeded(); | |
| return sync; | |
| } | |
| GLsizei GLES2DecoderImpl::InternalFormatSampleCountsHelper( | |
| GLenum target, | |
| GLenum internalformat, | |
| std::vector<GLint>* out_sample_counts) { | |
| DCHECK(out_sample_counts == nullptr || out_sample_counts->size() == 0); | |
| GLint num_sample_counts = 0; | |
| api()->glGetInternalformativFn(target, internalformat, GL_NUM_SAMPLE_COUNTS, | |
| 1, &num_sample_counts); | |
| bool remove_nonconformant_sample_counts = | |
| feature_info_->IsWebGLContext() && | |
| feature_info_->feature_flags().nv_internalformat_sample_query; | |
| if (out_sample_counts != nullptr || remove_nonconformant_sample_counts) { | |
| std::vector<GLint> sample_counts(num_sample_counts); | |
| api()->glGetInternalformativFn(target, internalformat, GL_SAMPLES, | |
| num_sample_counts, sample_counts.data()); | |
| if (remove_nonconformant_sample_counts) { | |
| ScopedGLErrorSuppressor suppressor( | |
| "GLES2DecoderImpl::InternalFormatSampleCountsHelper", | |
| error_state_.get()); | |
| auto is_nonconformant = [this, target, | |
| internalformat](GLint sample_count) { | |
| GLint conformant = GL_FALSE; | |
| api()->glGetInternalformatSampleivNVFn(target, internalformat, | |
| sample_count, GL_CONFORMANT_NV, | |
| 1, &conformant); | |
| // getInternalformatSampleivNV does not work for all formats on NVIDIA | |
| // Shield TV drivers. Assume that formats with large sample counts are | |
| // non-conformant in case the query generates an error. | |
| if (api()->glGetErrorFn() != GL_NO_ERROR) { | |
| return sample_count > 8; | |
| } | |
| return conformant == GL_FALSE; | |
| }; | |
| std::erase_if(sample_counts, is_nonconformant); | |
| num_sample_counts = sample_counts.size(); | |
| } | |
| if (out_sample_counts != nullptr) { | |
| *out_sample_counts = std::move(sample_counts); | |
| } | |
| } | |
| DCHECK(out_sample_counts == nullptr || | |
| out_sample_counts->size() == static_cast<size_t>(num_sample_counts)); | |
| return num_sample_counts; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetInternalformativ( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| if (!feature_info_->IsWebGL2OrES3Context()) | |
| return error::kUnknownCommand; | |
| const volatile gles2::cmds::GetInternalformativ& c = | |
| *static_cast<const volatile gles2::cmds::GetInternalformativ*>(cmd_data); | |
| GLenum target = static_cast<GLenum>(c.target); | |
| GLenum format = static_cast<GLenum>(c.format); | |
| GLenum pname = static_cast<GLenum>(c.pname); | |
| if (!validators_->render_buffer_target.IsValid(target)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM("glGetInternalformativ", target, "target"); | |
| return error::kNoError; | |
| } | |
| if (!validators_->render_buffer_format.IsValid(format)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM("glGetInternalformativ", format, | |
| "internalformat"); | |
| return error::kNoError; | |
| } | |
| if (!validators_->internal_format_parameter.IsValid(pname)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM("glGetInternalformativ", pname, "pname"); | |
| return error::kNoError; | |
| } | |
| typedef cmds::GetInternalformativ::Result Result; | |
| GLsizei num_sample_counts = 0; | |
| std::vector<GLint> sample_counts; | |
| GLsizei num_values = 0; | |
| base::span<GLint> values = {}; | |
| switch (pname) { | |
| case GL_NUM_SAMPLE_COUNTS: | |
| num_sample_counts = | |
| InternalFormatSampleCountsHelper(target, format, nullptr); | |
| num_values = 1; | |
| values = base::span_from_ref(num_sample_counts); | |
| break; | |
| case GL_SAMPLES: | |
| num_sample_counts = | |
| InternalFormatSampleCountsHelper(target, format, &sample_counts); | |
| num_values = num_sample_counts; | |
| values = sample_counts; | |
| break; | |
| default: | |
| NOTREACHED(); | |
| } | |
| uint32_t checked_size = 0; | |
| if (!Result::ComputeSize(num_values).AssignIfValid(&checked_size)) { | |
| return error::kOutOfBounds; | |
| } | |
| Result* result = GetSharedMemoryAs<Result*>( | |
| c.params_shm_id, c.params_shm_offset, checked_size); | |
| GLint* params = result ? result->GetData() : nullptr; | |
| if (params == nullptr) { | |
| return error::kOutOfBounds; | |
| } | |
| // Check that the client initialized the result. | |
| if (result->size != 0) { | |
| return error::kInvalidArguments; | |
| } | |
| std::copy(values.data(), | |
| values.subspan(base::checked_cast<size_t>(num_values)).data(), | |
| params); | |
| result->SetNumResults(num_values); | |
| return error::kNoError; | |
| } | |
| error::Error GLES2DecoderImpl::HandleGetBufferSubDataCHROMIUM( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile gles2::cmds::GetBufferSubDataCHROMIUM& c = | |
| *static_cast<const volatile gles2::cmds::GetBufferSubDataCHROMIUM*>( | |
| cmd_data); | |
| const char* func_name = "glGetBufferSubDataCHROMIUM"; | |
| GLenum target = c.target; | |
| GLintptr offset = static_cast<GLintptr>(c.offset); | |
| GLsizeiptr size = static_cast<GLsizeiptr>(c.size); | |
| uint32_t data_shm_id = c.data_shm_id; | |
| uint32_t data_shm_offset = c.data_shm_offset; | |
| if (!validators_->buffer_target.IsValid(target)) { | |
| LOCAL_SET_GL_ERROR_INVALID_ENUM(func_name, target, "target"); | |
| return error::kNoError; | |
| } | |
| if (size == 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, "length is zero"); | |
| return error::kNoError; | |
| } | |
| Buffer* buffer = buffer_manager()->RequestBufferAccess( | |
| &state_, error_state_.get(), target, offset, size, func_name); | |
| if (!buffer) { | |
| // An error is already set. | |
| return error::kNoError; | |
| } | |
| if (state_.bound_transform_feedback->active() && | |
| !state_.bound_transform_feedback->paused()) { | |
| size_t used_binding_count = | |
| state_.current_program->effective_transform_feedback_varyings().size(); | |
| if (state_.bound_transform_feedback->UsesBuffer( | |
| used_binding_count, buffer)) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name, | |
| "active transform feedback is using this buffer"); | |
| return error::kNoError; | |
| } | |
| } | |
| int8_t* mem = | |
| GetSharedMemoryAs<int8_t*>(data_shm_id, data_shm_offset, size); | |
| if (!mem) { | |
| return error::kOutOfBounds; | |
| } | |
| void* ptr = api()->glMapBufferRangeFn(target, offset, size, GL_MAP_READ_BIT); | |
| if (ptr == nullptr) { | |
| // This should mean GL_OUT_OF_MEMORY (or context loss). | |
| LOCAL_COPY_REAL_GL_ERRORS_TO_WRAPPER(func_name); | |
| return error::kNoError; | |
| } | |
| UNSAFE_TODO(memcpy(mem, ptr, size)); | |
| api()->glUnmapBufferFn(target); | |
| return error::kNoError; | |
| } | |
| void GLES2DecoderImpl::DoFramebufferMemorylessPixelLocalStorageANGLE( | |
| GLint plane, | |
| GLenum internalformat, | |
| GLbitfield usage) { | |
| NOTIMPLEMENTED(); | |
| } | |
| void GLES2DecoderImpl::DoFramebufferTexturePixelLocalStorageANGLE( | |
| GLint plane, | |
| GLuint client_texture_id, | |
| GLint level, | |
| GLint layer, | |
| GLbitfield usage) { | |
| NOTIMPLEMENTED(); | |
| } | |
| void GLES2DecoderImpl::DoFramebufferPixelLocalClearValuefvANGLE( | |
| GLint plane, | |
| const volatile GLfloat* value) { | |
| NOTIMPLEMENTED(); | |
| } | |
| void GLES2DecoderImpl::DoFramebufferPixelLocalClearValueivANGLE( | |
| GLint plane, | |
| const volatile GLint* value) { | |
| NOTIMPLEMENTED(); | |
| } | |
| void GLES2DecoderImpl::DoFramebufferPixelLocalClearValueuivANGLE( | |
| GLint plane, | |
| const volatile GLuint* value) { | |
| NOTIMPLEMENTED(); | |
| } | |
| void GLES2DecoderImpl::DoBeginPixelLocalStorageANGLE( | |
| GLsizei n, | |
| const volatile GLenum* loadops) { | |
| NOTIMPLEMENTED(); | |
| } | |
| void GLES2DecoderImpl::DoEndPixelLocalStorageANGLE( | |
| GLsizei n, | |
| const volatile GLenum* storeops) { | |
| NOTIMPLEMENTED(); | |
| } | |
| void GLES2DecoderImpl::DoEndPixelLocalStorageImplicitANGLE() { | |
| NOTIMPLEMENTED(); | |
| } | |
| void GLES2DecoderImpl::DoPixelLocalStorageBarrierANGLE() { | |
| NOTIMPLEMENTED(); | |
| } | |
| void GLES2DecoderImpl::DoFramebufferPixelLocalStorageInterruptANGLE() { | |
| NOTIMPLEMENTED(); | |
| } | |
| void GLES2DecoderImpl::DoFramebufferPixelLocalStorageRestoreANGLE() { | |
| NOTIMPLEMENTED(); | |
| } | |
| void GLES2DecoderImpl::DoGetFramebufferPixelLocalStorageParameterfvANGLE( | |
| GLint plane, | |
| GLenum pname, | |
| GLfloat* params, | |
| GLsizei params_size) { | |
| NOTIMPLEMENTED(); | |
| } | |
| void GLES2DecoderImpl::DoGetFramebufferPixelLocalStorageParameterivANGLE( | |
| GLint plane, | |
| GLenum pname, | |
| GLint* params, | |
| GLsizei params_size) { | |
| NOTIMPLEMENTED(); | |
| } | |
| void GLES2DecoderImpl::DoGetFramebufferPixelLocalStorageParameteruivANGLE( | |
| GLint plane, | |
| GLenum pname, | |
| GLuint* params, | |
| GLsizei params_size) { | |
| NOTIMPLEMENTED(); | |
| } | |
| // Note that GL_LOST_CONTEXT is specific to GLES. | |
| // For desktop GL we have to query the reset status proactively. | |
| void GLES2DecoderImpl::OnContextLostError() { | |
| if (!WasContextLost()) { | |
| // Need to lose current context before broadcasting! | |
| CheckResetStatus(); | |
| group_->LoseContexts(error::kUnknown); | |
| reset_by_robustness_extension_ = true; | |
| } | |
| } | |
| void GLES2DecoderImpl::OnOutOfMemoryError() { | |
| if (lose_context_when_out_of_memory_ && !WasContextLost()) { | |
| error::ContextLostReason other = error::kOutOfMemory; | |
| if (CheckResetStatus()) { | |
| other = error::kUnknown; | |
| } else { | |
| // Need to lose current context before broadcasting! | |
| MarkContextLost(error::kOutOfMemory); | |
| } | |
| group_->LoseContexts(other); | |
| } | |
| } | |
| const SamplerState& GLES2DecoderImpl::GetSamplerStateForTextureUnit( | |
| GLenum target, GLuint unit) { | |
| if (features().enable_samplers) { | |
| Sampler* sampler = state_.sampler_units[unit].get(); | |
| if (sampler) | |
| return sampler->sampler_state(); | |
| } | |
| TextureUnit& texture_unit = state_.texture_units[unit]; | |
| TextureRef* texture_ref = texture_unit.GetInfoForSamplerType(target); | |
| if (texture_ref) | |
| return texture_ref->texture()->sampler_state(); | |
| return default_sampler_state_; | |
| } | |
| bool GLES2DecoderImpl::ClearFramebufferForWorkaround(GLbitfield mask) { | |
| ScopedGLErrorSuppressor suppressor("GLES2DecoderImpl::ClearWorkaround", | |
| error_state_.get()); | |
| clear_framebuffer_blit_->ClearFramebuffer( | |
| this, state_.bound_transform_feedback.get(), | |
| gfx::Size(viewport_max_width_, viewport_max_height_), mask, | |
| state_.color_clear_red, state_.color_clear_green, state_.color_clear_blue, | |
| state_.color_clear_alpha, state_.depth_clear, state_.stencil_clear); | |
| // |suppressor|'s destructor silently discards GL_OUT_OF_MEMORY and | |
| // GL_CONTEXT_LOST_KHR, so peek here while the failure is still observable. | |
| // Callers that commit "cleared" bookkeeping must be able to tell that the | |
| // blit failed, otherwise an attachment that still holds uninitialized GPU | |
| // memory would be recorded as cleared. Peeking also routes GL_OUT_OF_MEMORY | |
| // through OnOutOfMemoryError() so lose_context_when_out_of_memory_ applies. | |
| // | |
| // The suppressor is deliberately kept: it is what keeps the blit's errors | |
| // from being attributed to unrelated later commands for the callers that | |
| // ignore the result. Surfacing the first error to those callers is bounded, | |
| // because ClearRealGLErrors() NOTREACHED()s on anything other than | |
| // GL_OUT_OF_MEMORY and GL_CONTEXT_LOST_KHR, i.e. the code already asserts | |
| // that those are the only errors this blit can produce. | |
| return LOCAL_PEEK_GL_ERROR("ClearFramebufferForWorkaround") == GL_NO_ERROR; | |
| } | |
| void GLES2DecoderImpl::RestoreAllExternalTextureBindingsIfNeeded() { | |
| if (texture_manager()->GetServiceIdGeneration() == | |
| texture_manager_service_id_generation_) | |
| return; | |
| // Texture manager's version has changed, so rebind all external textures | |
| // in case their service ids have changed. | |
| for (unsigned texture_unit_index = 0; | |
| texture_unit_index < state_.texture_units.size(); texture_unit_index++) { | |
| TextureUnit& texture_unit = state_.texture_units[texture_unit_index]; | |
| if (texture_unit.bind_target != GL_TEXTURE_EXTERNAL_OES) | |
| continue; | |
| if (TextureRef* texture_ref = | |
| texture_unit.bound_texture_external_oes.get()) { | |
| api()->glActiveTextureFn(GL_TEXTURE0 + texture_unit_index); | |
| api()->glBindTextureFn(GL_TEXTURE_EXTERNAL_OES, | |
| texture_ref->service_id()); | |
| } | |
| } | |
| api()->glActiveTextureFn(GL_TEXTURE0 + state_.active_texture_unit); | |
| texture_manager_service_id_generation_ = | |
| texture_manager()->GetServiceIdGeneration(); | |
| } | |
| void GLES2DecoderImpl::DoContextVisibilityHintCHROMIUM(GLboolean visibility) { | |
| if (feature_info_->IsWebGLContext()) | |
| context_->SetVisibility(visibility == GL_TRUE); | |
| } | |
| scoped_refptr<gpu::Buffer> GLES2DecoderImpl::GetShmBuffer(uint32_t shm_id) { | |
| return GetSharedMemoryBuffer(shm_id); | |
| } | |
| void GLES2DecoderImpl::DoWindowRectanglesEXT(GLenum mode, | |
| GLsizei n, | |
| const volatile GLint* box) { | |
| std::vector<GLint> box_copy(box, UNSAFE_TODO(box + (n * 4))); | |
| if (static_cast<size_t>(n) > state_.GetMaxWindowRectangles()) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glWindowRectanglesEXT", | |
| "count > GL_MAX_WINDOW_RECTANGLES_EXT"); | |
| return; | |
| } | |
| for (int i = 0; i < n; ++i) { | |
| int boxindex = i * 4; | |
| if (box_copy[boxindex + 2] < 0 || box_copy[boxindex + 3] < 0) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_VALUE, "glWindowRectanglesEXT", | |
| "negative box width or height"); | |
| return; | |
| } | |
| } | |
| state_.SetWindowRectangles(mode, box_copy); | |
| state_.UpdateWindowRectangles(); | |
| } | |
| void GLES2DecoderImpl::DoSetReadbackBufferShadowAllocationINTERNAL( | |
| GLuint buffer_id, | |
| GLuint shm_id, | |
| GLuint shm_offset, | |
| GLuint size) { | |
| static const char kFunctionName[] = "glSetBufferShadowAllocationINTERNAL"; | |
| scoped_refptr<Buffer> buffer = buffer_manager()->GetBuffer(buffer_id); | |
| if (!buffer) { | |
| LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, kFunctionName, "unknown buffer"); | |
| return; | |
| } | |
| if (static_cast<GLsizeiptr>(size) != buffer->size()) { | |
| MarkContextLost(error::kGuilty); | |
| group_->LoseContexts(error::kUnknown); | |
| return; | |
| } | |
| scoped_refptr<gpu::Buffer> shm = GetSharedMemoryBuffer(shm_id); | |
| buffer->SetReadbackShadowAllocation(shm, shm_offset); | |
| // All buffers in writes_submitted_but_not_completed_ should have shm | |
| // allocations. | |
| writes_submitted_but_not_completed_.insert(buffer); | |
| } | |
| void GLES2DecoderImpl::CompileShaderAndExitCommandProcessingEarly( | |
| Shader* shader) { | |
| // No need to call DoCompile or exit command processing early if the call | |
| // to DoCompile will be a no-op. | |
| if (!shader->CanCompile()) | |
| return; | |
| shader->DoCompile(); | |
| // Shader compilation can be very slow (see https://crbug.com/844780), Exit | |
| // command processing to allow for context preemption and GPU watchdog | |
| // checks. | |
| ExitCommandProcessingEarly(); | |
| } | |
| void GLES2DecoderImpl::ReportProgress() { | |
| if (group_) | |
| group_->ReportProgress(); | |
| } | |
| error::Error GLES2DecoderImpl::HandleSetActiveURLCHROMIUM( | |
| uint32_t immediate_data_size, | |
| const volatile void* cmd_data) { | |
| const volatile cmds::SetActiveURLCHROMIUM& c = | |
| *static_cast<const volatile cmds::SetActiveURLCHROMIUM*>(cmd_data); | |
| Bucket* url_bucket = GetBucket(c.url_bucket_id); | |
| static constexpr size_t kMaxStrLen = 1024; | |
| if (!url_bucket || url_bucket->size() == 0 || | |
| url_bucket->size() > kMaxStrLen) { | |
| return error::kInvalidArguments; | |
| } | |
| size_t size = url_bucket->size(); | |
| base::span<const uint8_t> url_bytes = url_bucket->GetDataAsByteSpan(0, size); | |
| if (url_bytes.empty()) { | |
| return error::kInvalidArguments; | |
| } | |
| GURL url(base::as_string_view(url_bytes)); | |
| client()->SetActiveURL(std::move(url)); | |
| return error::kNoError; | |
| } | |
| // Include the auto-generated part of this file. We split this because it means | |
| // we can easily edit the non-auto generated parts right here in this file | |
| // instead of having to edit some template or the code generator. | |
| #include "gpu/command_buffer/service/gles2_cmd_decoder_autogen.h" | |
| } // namespace gles2 | |
| } // namespace gpu |