blob: 139dc6593545ae8d1b0744b14f176da0d9847978 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// A class to emulate GLES2 over command buffers.
#include "gpu/command_buffer/client/gles2_implementation.h"
#include <GLES2/gl2ext.h>
#include <GLES2/gl2extchromium.h>
#include <GLES3/gl3.h>
#include <algorithm>
#include <limits>
#include <map>
#include <queue>
#include <set>
#include <sstream>
#include <string>
#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/numerics/safe_math.h"
#include "gpu/command_buffer/client/buffer_tracker.h"
#include "gpu/command_buffer/client/gpu_control.h"
#include "gpu/command_buffer/client/program_info_manager.h"
#include "gpu/command_buffer/client/query_tracker.h"
#include "gpu/command_buffer/client/transfer_buffer.h"
#include "gpu/command_buffer/client/vertex_array_object_manager.h"
#include "gpu/command_buffer/common/gles2_cmd_utils.h"
#include "gpu/command_buffer/common/trace_event.h"
#if defined(GPU_CLIENT_DEBUG)
#include "base/command_line.h"
#include "gpu/command_buffer/client/gpu_switches.h"
#endif
namespace gpu {
namespace gles2 {
// A 32-bit and 64-bit compatible way of converting a pointer to a GLuint.
static GLuint ToGLuint(const void* ptr) {
return static_cast<GLuint>(reinterpret_cast<size_t>(ptr));
}
#if !defined(_MSC_VER)
const size_t GLES2Implementation::kMaxSizeOfSimpleResult;
const unsigned int GLES2Implementation::kStartingOffset;
#endif
GLES2Implementation::GLStaticState::GLStaticState() {
}
GLES2Implementation::GLStaticState::~GLStaticState() {
}
GLES2Implementation::SingleThreadChecker::SingleThreadChecker(
GLES2Implementation* gles2_implementation)
: gles2_implementation_(gles2_implementation) {
CHECK_EQ(0, gles2_implementation_->use_count_);
++gles2_implementation_->use_count_;
}
GLES2Implementation::SingleThreadChecker::~SingleThreadChecker() {
--gles2_implementation_->use_count_;
CHECK_EQ(0, gles2_implementation_->use_count_);
}
GLES2Implementation::GLES2Implementation(
GLES2CmdHelper* helper,
ShareGroup* share_group,
TransferBufferInterface* transfer_buffer,
bool bind_generates_resource,
bool lose_context_when_out_of_memory,
bool support_client_side_arrays,
GpuControl* gpu_control)
: helper_(helper),
transfer_buffer_(transfer_buffer),
angle_pack_reverse_row_order_status_(kUnknownExtensionStatus),
chromium_framebuffer_multisample_(kUnknownExtensionStatus),
pack_alignment_(4),
unpack_alignment_(4),
unpack_flip_y_(false),
unpack_row_length_(0),
unpack_image_height_(0),
unpack_skip_rows_(0),
unpack_skip_pixels_(0),
unpack_skip_images_(0),
pack_reverse_row_order_(false),
active_texture_unit_(0),
bound_framebuffer_(0),
bound_read_framebuffer_(0),
bound_renderbuffer_(0),
bound_valuebuffer_(0),
current_program_(0),
bound_array_buffer_id_(0),
bound_pixel_pack_transfer_buffer_id_(0),
bound_pixel_unpack_transfer_buffer_id_(0),
async_upload_token_(0),
async_upload_sync_(NULL),
async_upload_sync_shm_id_(0),
async_upload_sync_shm_offset_(0),
error_bits_(0),
debug_(false),
lose_context_when_out_of_memory_(lose_context_when_out_of_memory),
support_client_side_arrays_(support_client_side_arrays),
use_count_(0),
error_message_callback_(NULL),
current_trace_stack_(0),
gpu_control_(gpu_control),
capabilities_(gpu_control->GetCapabilities()),
weak_ptr_factory_(this) {
DCHECK(helper);
DCHECK(transfer_buffer);
DCHECK(gpu_control);
std::stringstream ss;
ss << std::hex << this;
this_in_hex_ = ss.str();
GPU_CLIENT_LOG_CODE_BLOCK({
debug_ = base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableGPUClientLogging);
});
share_group_ =
(share_group ? share_group : new ShareGroup(bind_generates_resource));
DCHECK(share_group_->bind_generates_resource() == bind_generates_resource);
memset(&reserved_ids_, 0, sizeof(reserved_ids_));
}
bool GLES2Implementation::Initialize(
unsigned int starting_transfer_buffer_size,
unsigned int min_transfer_buffer_size,
unsigned int max_transfer_buffer_size,
unsigned int mapped_memory_limit) {
TRACE_EVENT0("gpu", "GLES2Implementation::Initialize");
DCHECK_GE(starting_transfer_buffer_size, min_transfer_buffer_size);
DCHECK_LE(starting_transfer_buffer_size, max_transfer_buffer_size);
DCHECK_GE(min_transfer_buffer_size, kStartingOffset);
if (!transfer_buffer_->Initialize(
starting_transfer_buffer_size,
kStartingOffset,
min_transfer_buffer_size,
max_transfer_buffer_size,
kAlignment,
kSizeToFlush)) {
return false;
}
mapped_memory_.reset(
new MappedMemoryManager(
helper_,
base::Bind(&GLES2Implementation::PollAsyncUploads,
// The mapped memory manager is owned by |this| here, and
// since its destroyed before before we destroy ourselves
// we don't need extra safety measures for this closure.
base::Unretained(this)),
mapped_memory_limit));
unsigned chunk_size = 2 * 1024 * 1024;
if (mapped_memory_limit != kNoLimit) {
// Use smaller chunks if the client is very memory conscientious.
chunk_size = std::min(mapped_memory_limit / 4, chunk_size);
}
mapped_memory_->set_chunk_size_multiple(chunk_size);
GLStaticState::ShaderPrecisionMap* shader_precisions =
&static_state_.shader_precisions;
capabilities_.VisitPrecisions([shader_precisions](
GLenum shader, GLenum type, Capabilities::ShaderPrecision* result) {
const GLStaticState::ShaderPrecisionKey key(shader, type);
cmds::GetShaderPrecisionFormat::Result cached_result = {
true, result->min_range, result->max_range, result->precision};
shader_precisions->insert(std::make_pair(key, cached_result));
});
util_.set_num_compressed_texture_formats(
capabilities_.num_compressed_texture_formats);
util_.set_num_shader_binary_formats(capabilities_.num_shader_binary_formats);
texture_units_.reset(
new TextureUnit[capabilities_.max_combined_texture_image_units]);
query_tracker_.reset(new QueryTracker(mapped_memory_.get()));
buffer_tracker_.reset(new BufferTracker(mapped_memory_.get()));
query_id_allocator_.reset(new IdAllocator());
if (support_client_side_arrays_) {
GetIdHandler(id_namespaces::kBuffers)->MakeIds(
this, kClientSideArrayId, arraysize(reserved_ids_), &reserved_ids_[0]);
}
vertex_array_object_manager_.reset(new VertexArrayObjectManager(
capabilities_.max_vertex_attribs, reserved_ids_[0], reserved_ids_[1],
support_client_side_arrays_));
// GL_BIND_GENERATES_RESOURCE_CHROMIUM state must be the same
// on Client & Service.
if (capabilities_.bind_generates_resource_chromium !=
(share_group_->bind_generates_resource() ? 1 : 0)) {
SetGLError(GL_INVALID_OPERATION,
"Initialize",
"Service bind_generates_resource mismatch.");
return false;
}
return true;
}
GLES2Implementation::~GLES2Implementation() {
// Make sure the queries are finished otherwise we'll delete the
// shared memory (mapped_memory_) which will free the memory used
// by the queries. The GPU process when validating that memory is still
// shared will fail and abort (ie, it will stop running).
WaitForCmd();
query_tracker_.reset();
// GLES2Implementation::Initialize() could fail before allocating
// reserved_ids_, so we need delete them carefully.
if (support_client_side_arrays_ && reserved_ids_[0]) {
DeleteBuffers(arraysize(reserved_ids_), &reserved_ids_[0]);
}
// Release remaining BufferRange mem; This is when a MapBufferRange() is
// called but not the UnmapBuffer() pair.
ClearMappedBufferRangeMap();
// Release any per-context data in share group.
share_group_->FreeContext(this);
buffer_tracker_.reset();
FreeAllAsyncUploadBuffers();
if (async_upload_sync_) {
mapped_memory_->Free(async_upload_sync_);
async_upload_sync_ = NULL;
}
// Make sure the commands make it the service.
WaitForCmd();
}
GLES2CmdHelper* GLES2Implementation::helper() const {
return helper_;
}
IdHandlerInterface* GLES2Implementation::GetIdHandler(int namespace_id) const {
return share_group_->GetIdHandler(namespace_id);
}
IdAllocator* GLES2Implementation::GetIdAllocator(int namespace_id) const {
if (namespace_id == id_namespaces::kQueries)
return query_id_allocator_.get();
NOTREACHED();
return NULL;
}
void* GLES2Implementation::GetResultBuffer() {
return transfer_buffer_->GetResultBuffer();
}
int32 GLES2Implementation::GetResultShmId() {
return transfer_buffer_->GetShmId();
}
uint32 GLES2Implementation::GetResultShmOffset() {
return transfer_buffer_->GetResultOffset();
}
void GLES2Implementation::FreeUnusedSharedMemory() {
mapped_memory_->FreeUnused();
}
void GLES2Implementation::FreeEverything() {
FreeAllAsyncUploadBuffers();
WaitForCmd();
query_tracker_->Shrink();
FreeUnusedSharedMemory();
transfer_buffer_->Free();
helper_->FreeRingBuffer();
}
void GLES2Implementation::RunIfContextNotLost(const base::Closure& callback) {
if (!helper_->IsContextLost())
callback.Run();
}
void GLES2Implementation::SignalSyncPoint(uint32 sync_point,
const base::Closure& callback) {
gpu_control_->SignalSyncPoint(
sync_point,
base::Bind(&GLES2Implementation::RunIfContextNotLost,
weak_ptr_factory_.GetWeakPtr(),
callback));
}
void GLES2Implementation::SignalQuery(uint32 query,
const base::Closure& callback) {
// Flush previously entered commands to ensure ordering with any
// glBeginQueryEXT() calls that may have been put into the context.
ShallowFlushCHROMIUM();
gpu_control_->SignalQuery(
query,
base::Bind(&GLES2Implementation::RunIfContextNotLost,
weak_ptr_factory_.GetWeakPtr(),
callback));
}
void GLES2Implementation::SetSurfaceVisible(bool visible) {
TRACE_EVENT1(
"gpu", "GLES2Implementation::SetSurfaceVisible", "visible", visible);
// TODO(piman): This probably should be ShallowFlushCHROMIUM().
Flush();
gpu_control_->SetSurfaceVisible(visible);
if (!visible)
FreeEverything();
}
void GLES2Implementation::WaitForCmd() {
TRACE_EVENT0("gpu", "GLES2::WaitForCmd");
helper_->CommandBufferHelper::Finish();
}
bool GLES2Implementation::IsExtensionAvailable(const char* ext) {
const char* extensions =
reinterpret_cast<const char*>(GetStringHelper(GL_EXTENSIONS));
if (!extensions)
return false;
int length = strlen(ext);
while (true) {
int n = strcspn(extensions, " ");
if (n == length && 0 == strncmp(ext, extensions, length)) {
return true;
}
if ('\0' == extensions[n]) {
return false;
}
extensions += n + 1;
}
}
bool GLES2Implementation::IsExtensionAvailableHelper(
const char* extension, ExtensionStatus* status) {
switch (*status) {
case kAvailableExtensionStatus:
return true;
case kUnavailableExtensionStatus:
return false;
default: {
bool available = IsExtensionAvailable(extension);
*status = available ? kAvailableExtensionStatus :
kUnavailableExtensionStatus;
return available;
}
}
}
bool GLES2Implementation::IsAnglePackReverseRowOrderAvailable() {
return IsExtensionAvailableHelper(
"GL_ANGLE_pack_reverse_row_order",
&angle_pack_reverse_row_order_status_);
}
bool GLES2Implementation::IsChromiumFramebufferMultisampleAvailable() {
return IsExtensionAvailableHelper(
"GL_CHROMIUM_framebuffer_multisample",
&chromium_framebuffer_multisample_);
}
const std::string& GLES2Implementation::GetLogPrefix() const {
const std::string& prefix(debug_marker_manager_.GetMarker());
return prefix.empty() ? this_in_hex_ : prefix;
}
GLenum GLES2Implementation::GetError() {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glGetError()");
GLenum err = GetGLError();
GPU_CLIENT_LOG("returned " << GLES2Util::GetStringError(err));
return err;
}
GLenum GLES2Implementation::GetClientSideGLError() {
if (error_bits_ == 0) {
return GL_NO_ERROR;
}
GLenum error = GL_NO_ERROR;
for (uint32 mask = 1; mask != 0; mask = mask << 1) {
if ((error_bits_ & mask) != 0) {
error = GLES2Util::GLErrorBitToGLError(mask);
break;
}
}
error_bits_ &= ~GLES2Util::GLErrorToErrorBit(error);
return error;
}
GLenum GLES2Implementation::GetGLError() {
TRACE_EVENT0("gpu", "GLES2::GetGLError");
// Check the GL error first, then our wrapped error.
typedef cmds::GetError::Result Result;
Result* result = GetResultAs<Result*>();
// If we couldn't allocate a result the context is lost.
if (!result) {
return GL_NO_ERROR;
}
*result = GL_NO_ERROR;
helper_->GetError(GetResultShmId(), GetResultShmOffset());
WaitForCmd();
GLenum error = *result;
if (error == GL_NO_ERROR) {
error = GetClientSideGLError();
} else {
// There was an error, clear the corresponding wrapped error.
error_bits_ &= ~GLES2Util::GLErrorToErrorBit(error);
}
return error;
}
#if defined(GL_CLIENT_FAIL_GL_ERRORS)
void GLES2Implementation::FailGLError(GLenum error) {
if (error != GL_NO_ERROR) {
NOTREACHED() << "Error";
}
}
// NOTE: Calling GetGLError overwrites data in the result buffer.
void GLES2Implementation::CheckGLError() {
FailGLError(GetGLError());
}
#endif // defined(GPU_CLIENT_FAIL_GL_ERRORS)
void GLES2Implementation::SetGLError(
GLenum error, const char* function_name, const char* msg) {
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] Client Synthesized Error: "
<< GLES2Util::GetStringError(error) << ": "
<< function_name << ": " << msg);
FailGLError(error);
if (msg) {
last_error_ = msg;
}
if (error_message_callback_) {
std::string temp(GLES2Util::GetStringError(error) + " : " +
function_name + ": " + (msg ? msg : ""));
error_message_callback_->OnErrorMessage(temp.c_str(), 0);
}
error_bits_ |= GLES2Util::GLErrorToErrorBit(error);
if (error == GL_OUT_OF_MEMORY && lose_context_when_out_of_memory_) {
helper_->LoseContextCHROMIUM(GL_GUILTY_CONTEXT_RESET_ARB,
GL_UNKNOWN_CONTEXT_RESET_ARB);
}
}
void GLES2Implementation::SetGLErrorInvalidEnum(
const char* function_name, GLenum value, const char* label) {
SetGLError(GL_INVALID_ENUM, function_name,
(std::string(label) + " was " +
GLES2Util::GetStringEnum(value)).c_str());
}
bool GLES2Implementation::GetBucketContents(uint32 bucket_id,
std::vector<int8>* data) {
TRACE_EVENT0("gpu", "GLES2::GetBucketContents");
DCHECK(data);
const uint32 kStartSize = 32 * 1024;
ScopedTransferBufferPtr buffer(kStartSize, helper_, transfer_buffer_);
if (!buffer.valid()) {
return false;
}
typedef cmd::GetBucketStart::Result Result;
Result* result = GetResultAs<Result*>();
if (!result) {
return false;
}
*result = 0;
helper_->GetBucketStart(
bucket_id, GetResultShmId(), GetResultShmOffset(),
buffer.size(), buffer.shm_id(), buffer.offset());
WaitForCmd();
uint32 size = *result;
data->resize(size);
if (size > 0u) {
uint32 offset = 0;
while (size) {
if (!buffer.valid()) {
buffer.Reset(size);
if (!buffer.valid()) {
return false;
}
helper_->GetBucketData(
bucket_id, offset, buffer.size(), buffer.shm_id(), buffer.offset());
WaitForCmd();
}
uint32 size_to_copy = std::min(size, buffer.size());
memcpy(&(*data)[offset], buffer.address(), size_to_copy);
offset += size_to_copy;
size -= size_to_copy;
buffer.Release();
}
// Free the bucket. This is not required but it does free up the memory.
// and we don't have to wait for the result so from the client's perspective
// it's cheap.
helper_->SetBucketSize(bucket_id, 0);
}
return true;
}
void GLES2Implementation::SetBucketContents(
uint32 bucket_id, const void* data, size_t size) {
DCHECK(data);
helper_->SetBucketSize(bucket_id, size);
if (size > 0u) {
uint32 offset = 0;
while (size) {
ScopedTransferBufferPtr buffer(size, helper_, transfer_buffer_);
if (!buffer.valid()) {
return;
}
memcpy(buffer.address(), static_cast<const int8*>(data) + offset,
buffer.size());
helper_->SetBucketData(
bucket_id, offset, buffer.size(), buffer.shm_id(), buffer.offset());
offset += buffer.size();
size -= buffer.size();
}
}
}
void GLES2Implementation::SetBucketAsCString(
uint32 bucket_id, const char* str) {
// NOTE: strings are passed NULL terminated. That means the empty
// string will have a size of 1 and no-string will have a size of 0
if (str) {
SetBucketContents(bucket_id, str, strlen(str) + 1);
} else {
helper_->SetBucketSize(bucket_id, 0);
}
}
bool GLES2Implementation::GetBucketAsString(
uint32 bucket_id, std::string* str) {
DCHECK(str);
std::vector<int8> data;
// NOTE: strings are passed NULL terminated. That means the empty
// string will have a size of 1 and no-string will have a size of 0
if (!GetBucketContents(bucket_id, &data)) {
return false;
}
if (data.empty()) {
return false;
}
str->assign(&data[0], &data[0] + data.size() - 1);
return true;
}
void GLES2Implementation::SetBucketAsString(
uint32 bucket_id, const std::string& str) {
// NOTE: strings are passed NULL terminated. That means the empty
// string will have a size of 1 and no-string will have a size of 0
SetBucketContents(bucket_id, str.c_str(), str.size() + 1);
}
void GLES2Implementation::Disable(GLenum cap) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glDisable("
<< GLES2Util::GetStringCapability(cap) << ")");
bool changed = false;
if (!state_.SetCapabilityState(cap, false, &changed) || changed) {
helper_->Disable(cap);
}
CheckGLError();
}
void GLES2Implementation::Enable(GLenum cap) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glEnable("
<< GLES2Util::GetStringCapability(cap) << ")");
bool changed = false;
if (!state_.SetCapabilityState(cap, true, &changed) || changed) {
helper_->Enable(cap);
}
CheckGLError();
}
GLboolean GLES2Implementation::IsEnabled(GLenum cap) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glIsEnabled("
<< GLES2Util::GetStringCapability(cap) << ")");
bool state = false;
if (!state_.GetEnabled(cap, &state)) {
typedef cmds::IsEnabled::Result Result;
Result* result = GetResultAs<Result*>();
if (!result) {
return GL_FALSE;
}
*result = 0;
helper_->IsEnabled(cap, GetResultShmId(), GetResultShmOffset());
WaitForCmd();
state = (*result) != 0;
}
GPU_CLIENT_LOG("returned " << state);
CheckGLError();
return state;
}
bool GLES2Implementation::GetHelper(GLenum pname, GLint* params) {
// TODO(zmo): For all the BINDING points, there is a possibility where
// resources are shared among multiple contexts, that the cached points
// are invalid. It is not a problem for now, but once we allow resource
// sharing in WebGL, we need to implement a mechanism to allow correct
// client side binding points tracking. crbug.com/465562.
switch (pname) {
case GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS:
*params = capabilities_.max_combined_texture_image_units;
return true;
case GL_MAX_CUBE_MAP_TEXTURE_SIZE:
*params = capabilities_.max_cube_map_texture_size;
return true;
case GL_MAX_FRAGMENT_UNIFORM_VECTORS:
*params = capabilities_.max_fragment_uniform_vectors;
return true;
case GL_MAX_RENDERBUFFER_SIZE:
*params = capabilities_.max_renderbuffer_size;
return true;
case GL_MAX_TEXTURE_IMAGE_UNITS:
*params = capabilities_.max_texture_image_units;
return true;
case GL_MAX_TEXTURE_SIZE:
*params = capabilities_.max_texture_size;
return true;
case GL_MAX_VARYING_VECTORS:
*params = capabilities_.max_varying_vectors;
return true;
case GL_MAX_VERTEX_ATTRIBS:
*params = capabilities_.max_vertex_attribs;
return true;
case GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS:
*params = capabilities_.max_vertex_texture_image_units;
return true;
case GL_MAX_VERTEX_UNIFORM_VECTORS:
*params = capabilities_.max_vertex_uniform_vectors;
return true;
case GL_NUM_COMPRESSED_TEXTURE_FORMATS:
*params = capabilities_.num_compressed_texture_formats;
return true;
case GL_NUM_SHADER_BINARY_FORMATS:
*params = capabilities_.num_shader_binary_formats;
return true;
case GL_ARRAY_BUFFER_BINDING:
*params = bound_array_buffer_id_;
return true;
case GL_ELEMENT_ARRAY_BUFFER_BINDING:
*params =
vertex_array_object_manager_->bound_element_array_buffer();
return true;
case GL_PIXEL_PACK_TRANSFER_BUFFER_BINDING_CHROMIUM:
*params = bound_pixel_pack_transfer_buffer_id_;
return true;
case GL_PIXEL_UNPACK_TRANSFER_BUFFER_BINDING_CHROMIUM:
*params = bound_pixel_unpack_transfer_buffer_id_;
return true;
case GL_ACTIVE_TEXTURE:
*params = active_texture_unit_ + GL_TEXTURE0;
return true;
case GL_TEXTURE_BINDING_2D:
*params = texture_units_[active_texture_unit_].bound_texture_2d;
return true;
case GL_TEXTURE_BINDING_CUBE_MAP:
*params = texture_units_[active_texture_unit_].bound_texture_cube_map;
return true;
case GL_TEXTURE_BINDING_EXTERNAL_OES:
*params =
texture_units_[active_texture_unit_].bound_texture_external_oes;
return true;
case GL_FRAMEBUFFER_BINDING:
*params = bound_framebuffer_;
return true;
case GL_READ_FRAMEBUFFER_BINDING:
if (IsChromiumFramebufferMultisampleAvailable()) {
*params = bound_read_framebuffer_;
return true;
}
return false;
case GL_RENDERBUFFER_BINDING:
*params = bound_renderbuffer_;
return true;
case GL_MAX_UNIFORM_BUFFER_BINDINGS:
*params = capabilities_.max_uniform_buffer_bindings;
return true;
case GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS:
*params = capabilities_.max_transform_feedback_separate_attribs;
return true;
case GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT:
*params = capabilities_.uniform_buffer_offset_alignment;
return true;
// TODO(zmo): Support ES3 pnames.
default:
return false;
}
}
bool GLES2Implementation::GetBooleanvHelper(GLenum pname, GLboolean* params) {
// TODO(gman): Make this handle pnames that return more than 1 value.
GLint value;
if (!GetHelper(pname, &value)) {
return false;
}
*params = static_cast<GLboolean>(value);
return true;
}
bool GLES2Implementation::GetFloatvHelper(GLenum pname, GLfloat* params) {
// TODO(gman): Make this handle pnames that return more than 1 value.
GLint value;
if (!GetHelper(pname, &value)) {
return false;
}
*params = static_cast<GLfloat>(value);
return true;
}
bool GLES2Implementation::GetIntegervHelper(GLenum pname, GLint* params) {
return GetHelper(pname, params);
}
bool GLES2Implementation::GetInternalformativHelper(
GLenum target, GLenum format, GLenum pname, GLsizei bufSize,
GLint* params) {
// TODO(zmo): Implement the client side caching.
return false;
}
bool GLES2Implementation::GetSyncivHelper(
GLsync sync, GLenum pname, GLsizei bufsize, GLsizei* length,
GLint* values) {
GLint value = 0;
switch (pname) {
case GL_OBJECT_TYPE:
value = GL_SYNC_FENCE;
break;
case GL_SYNC_CONDITION:
value = GL_SYNC_GPU_COMMANDS_COMPLETE;
break;
case GL_SYNC_FLAGS:
value = 0;
break;
default:
return false;
}
if (bufsize > 0) {
DCHECK(values);
*values = value;
}
if (length) {
*length = 1;
}
return true;
}
GLuint GLES2Implementation::GetMaxValueInBufferCHROMIUMHelper(
GLuint buffer_id, GLsizei count, GLenum type, GLuint offset) {
typedef cmds::GetMaxValueInBufferCHROMIUM::Result Result;
Result* result = GetResultAs<Result*>();
if (!result) {
return 0;
}
*result = 0;
helper_->GetMaxValueInBufferCHROMIUM(
buffer_id, count, type, offset, GetResultShmId(), GetResultShmOffset());
WaitForCmd();
return *result;
}
GLuint GLES2Implementation::GetMaxValueInBufferCHROMIUM(
GLuint buffer_id, GLsizei count, GLenum type, GLuint offset) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glGetMaxValueInBufferCHROMIUM("
<< buffer_id << ", " << count << ", "
<< GLES2Util::GetStringGetMaxIndexType(type)
<< ", " << offset << ")");
GLuint result = GetMaxValueInBufferCHROMIUMHelper(
buffer_id, count, type, offset);
GPU_CLIENT_LOG("returned " << result);
CheckGLError();
return result;
}
void GLES2Implementation::RestoreElementAndArrayBuffers(bool restore) {
if (restore) {
RestoreArrayBuffer(restore);
// Restore the element array binding.
// We only need to restore it if it wasn't a client side array.
if (vertex_array_object_manager_->bound_element_array_buffer() == 0) {
helper_->BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
}
}
void GLES2Implementation::RestoreArrayBuffer(bool restore) {
if (restore) {
// Restore the user's current binding.
helper_->BindBuffer(GL_ARRAY_BUFFER, bound_array_buffer_id_);
}
}
void GLES2Implementation::DrawElements(
GLenum mode, GLsizei count, GLenum type, const void* indices) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glDrawElements("
<< GLES2Util::GetStringDrawMode(mode) << ", "
<< count << ", "
<< GLES2Util::GetStringIndexType(type) << ", "
<< static_cast<const void*>(indices) << ")");
DrawElementsImpl(mode, count, type, indices, "glDrawRangeElements");
}
void GLES2Implementation::DrawRangeElements(
GLenum mode, GLuint start, GLuint end,
GLsizei count, GLenum type, const void* indices) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glDrawRangeElements("
<< GLES2Util::GetStringDrawMode(mode) << ", "
<< start << ", " << end << ", " << count << ", "
<< GLES2Util::GetStringIndexType(type) << ", "
<< static_cast<const void*>(indices) << ")");
if (end < start) {
SetGLError(GL_INVALID_VALUE, "glDrawRangeElements", "end < start");
return;
}
DrawElementsImpl(mode, count, type, indices, "glDrawRangeElements");
}
void GLES2Implementation::DrawElementsImpl(
GLenum mode, GLsizei count, GLenum type, const void* indices,
const char* func_name) {
if (count < 0) {
SetGLError(GL_INVALID_VALUE, func_name, "count < 0");
return;
}
bool simulated = false;
GLuint offset = ToGLuint(indices);
if (count > 0) {
if (vertex_array_object_manager_->bound_element_array_buffer() != 0 &&
!ValidateOffset(func_name, reinterpret_cast<GLintptr>(indices))) {
return;
}
if (!vertex_array_object_manager_->SetupSimulatedIndexAndClientSideBuffers(
func_name, this, helper_, count, type, 0, indices,
&offset, &simulated)) {
return;
}
}
helper_->DrawElements(mode, count, type, offset);
RestoreElementAndArrayBuffers(simulated);
CheckGLError();
}
void GLES2Implementation::Flush() {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glFlush()");
// Insert the cmd to call glFlush
helper_->Flush();
// Flush our command buffer
// (tell the service to execute up to the flush cmd.)
helper_->CommandBufferHelper::Flush();
}
void GLES2Implementation::ShallowFlushCHROMIUM() {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glShallowFlushCHROMIUM()");
// Flush our command buffer
// (tell the service to execute up to the flush cmd.)
helper_->CommandBufferHelper::Flush();
// TODO(piman): Add the FreeEverything() logic here.
}
void GLES2Implementation::OrderingBarrierCHROMIUM() {
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glOrderingBarrierCHROMIUM");
// Flush command buffer at the GPU channel level. May be implemented as
// Flush().
helper_->CommandBufferHelper::OrderingBarrier();
}
void GLES2Implementation::Finish() {
GPU_CLIENT_SINGLE_THREAD_CHECK();
FinishHelper();
}
void GLES2Implementation::ShallowFinishCHROMIUM() {
GPU_CLIENT_SINGLE_THREAD_CHECK();
TRACE_EVENT0("gpu", "GLES2::ShallowFinishCHROMIUM");
// Flush our command buffer (tell the service to execute up to the flush cmd
// and don't return until it completes).
helper_->CommandBufferHelper::Finish();
}
void GLES2Implementation::FinishHelper() {
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glFinish()");
TRACE_EVENT0("gpu", "GLES2::Finish");
// Insert the cmd to call glFinish
helper_->Finish();
// Finish our command buffer
// (tell the service to execute up to the Finish cmd and wait for it to
// execute.)
helper_->CommandBufferHelper::Finish();
}
void GLES2Implementation::SwapBuffers() {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glSwapBuffers()");
// TODO(piman): Strictly speaking we'd want to insert the token after the
// swap, but the state update with the updated token might not have happened
// by the time the SwapBuffer callback gets called, forcing us to synchronize
// with the GPU process more than needed. So instead, make it happen before.
// All it means is that we could be slightly looser on the kMaxSwapBuffers
// semantics if the client doesn't use the callback mechanism, and by chance
// the scheduler yields between the InsertToken and the SwapBuffers.
swap_buffers_tokens_.push(helper_->InsertToken());
helper_->SwapBuffers();
helper_->CommandBufferHelper::Flush();
// Wait if we added too many swap buffers. Add 1 to kMaxSwapBuffers to
// compensate for TODO above.
if (swap_buffers_tokens_.size() > kMaxSwapBuffers + 1) {
helper_->WaitForToken(swap_buffers_tokens_.front());
swap_buffers_tokens_.pop();
}
}
void GLES2Implementation::SwapInterval(int interval) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glSwapInterval("
<< interval << ")");
helper_->SwapInterval(interval);
}
void GLES2Implementation::BindAttribLocation(
GLuint program, GLuint index, const char* name) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glBindAttribLocation("
<< program << ", " << index << ", " << name << ")");
SetBucketAsString(kResultBucketId, name);
helper_->BindAttribLocationBucket(program, index, kResultBucketId);
helper_->SetBucketSize(kResultBucketId, 0);
CheckGLError();
}
void GLES2Implementation::BindUniformLocationCHROMIUM(
GLuint program, GLint location, const char* name) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glBindUniformLocationCHROMIUM("
<< program << ", " << location << ", " << name << ")");
SetBucketAsString(kResultBucketId, name);
helper_->BindUniformLocationCHROMIUMBucket(
program, location, kResultBucketId);
helper_->SetBucketSize(kResultBucketId, 0);
CheckGLError();
}
void GLES2Implementation::GetVertexAttribPointerv(
GLuint index, GLenum pname, void** ptr) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glGetVertexAttribPointer("
<< index << ", " << GLES2Util::GetStringVertexPointer(pname) << ", "
<< static_cast<void*>(ptr) << ")");
GPU_CLIENT_LOG_CODE_BLOCK(int32 num_results = 1);
if (!vertex_array_object_manager_->GetAttribPointer(index, pname, ptr)) {
TRACE_EVENT0("gpu", "GLES2::GetVertexAttribPointerv");
typedef cmds::GetVertexAttribPointerv::Result Result;
Result* result = GetResultAs<Result*>();
if (!result) {
return;
}
result->SetNumResults(0);
helper_->GetVertexAttribPointerv(
index, pname, GetResultShmId(), GetResultShmOffset());
WaitForCmd();
result->CopyResult(ptr);
GPU_CLIENT_LOG_CODE_BLOCK(num_results = result->GetNumResults());
}
GPU_CLIENT_LOG_CODE_BLOCK({
for (int32 i = 0; i < num_results; ++i) {
GPU_CLIENT_LOG(" " << i << ": " << ptr[i]);
}
});
CheckGLError();
}
bool GLES2Implementation::DeleteProgramHelper(GLuint program) {
if (!GetIdHandler(id_namespaces::kProgramsAndShaders)->FreeIds(
this, 1, &program, &GLES2Implementation::DeleteProgramStub)) {
SetGLError(
GL_INVALID_VALUE,
"glDeleteProgram", "id not created by this context.");
return false;
}
if (program == current_program_) {
current_program_ = 0;
}
return true;
}
void GLES2Implementation::DeleteProgramStub(
GLsizei n, const GLuint* programs) {
DCHECK_EQ(1, n);
share_group_->program_info_manager()->DeleteInfo(programs[0]);
helper_->DeleteProgram(programs[0]);
}
bool GLES2Implementation::DeleteShaderHelper(GLuint shader) {
if (!GetIdHandler(id_namespaces::kProgramsAndShaders)->FreeIds(
this, 1, &shader, &GLES2Implementation::DeleteShaderStub)) {
SetGLError(
GL_INVALID_VALUE,
"glDeleteShader", "id not created by this context.");
return false;
}
return true;
}
void GLES2Implementation::DeleteShaderStub(
GLsizei n, const GLuint* shaders) {
DCHECK_EQ(1, n);
share_group_->program_info_manager()->DeleteInfo(shaders[0]);
helper_->DeleteShader(shaders[0]);
}
void GLES2Implementation::DeleteSyncHelper(GLsync sync) {
GLuint sync_uint = ToGLuint(sync);
if (!GetIdHandler(id_namespaces::kSyncs)->FreeIds(
this, 1, &sync_uint, &GLES2Implementation::DeleteSyncStub)) {
SetGLError(
GL_INVALID_VALUE,
"glDeleteSync", "id not created by this context.");
}
}
void GLES2Implementation::DeleteSyncStub(GLsizei n, const GLuint* syncs) {
DCHECK_EQ(1, n);
helper_->DeleteSync(syncs[0]);
}
GLint GLES2Implementation::GetAttribLocationHelper(
GLuint program, const char* name) {
typedef cmds::GetAttribLocation::Result Result;
Result* result = GetResultAs<Result*>();
if (!result) {
return -1;
}
*result = -1;
SetBucketAsCString(kResultBucketId, name);
helper_->GetAttribLocation(
program, kResultBucketId, GetResultShmId(), GetResultShmOffset());
WaitForCmd();
helper_->SetBucketSize(kResultBucketId, 0);
return *result;
}
GLint GLES2Implementation::GetAttribLocation(
GLuint program, const char* name) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glGetAttribLocation(" << program
<< ", " << name << ")");
TRACE_EVENT0("gpu", "GLES2::GetAttribLocation");
GLint loc = share_group_->program_info_manager()->GetAttribLocation(
this, program, name);
GPU_CLIENT_LOG("returned " << loc);
CheckGLError();
return loc;
}
GLint GLES2Implementation::GetUniformLocationHelper(
GLuint program, const char* name) {
typedef cmds::GetUniformLocation::Result Result;
Result* result = GetResultAs<Result*>();
if (!result) {
return -1;
}
*result = -1;
SetBucketAsCString(kResultBucketId, name);
helper_->GetUniformLocation(program, kResultBucketId,
GetResultShmId(), GetResultShmOffset());
WaitForCmd();
helper_->SetBucketSize(kResultBucketId, 0);
return *result;
}
GLint GLES2Implementation::GetUniformLocation(
GLuint program, const char* name) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glGetUniformLocation(" << program
<< ", " << name << ")");
TRACE_EVENT0("gpu", "GLES2::GetUniformLocation");
GLint loc = share_group_->program_info_manager()->GetUniformLocation(
this, program, name);
GPU_CLIENT_LOG("returned " << loc);
CheckGLError();
return loc;
}
bool GLES2Implementation::GetUniformIndicesHelper(
GLuint program, GLsizei count, const char* const* names, GLuint* indices) {
typedef cmds::GetUniformIndices::Result Result;
Result* result = GetResultAs<Result*>();
if (!result) {
return false;
}
result->SetNumResults(0);
if (!PackStringsToBucket(count, names, NULL, "glGetUniformIndices")) {
return false;
}
helper_->GetUniformIndices(program, kResultBucketId,
GetResultShmId(), GetResultShmOffset());
WaitForCmd();
if (result->GetNumResults() != count) {
return false;
}
result->CopyResult(indices);
return true;
}
void GLES2Implementation::GetUniformIndices(
GLuint program, GLsizei count, const char* const* names, GLuint* indices) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glGetUniformIndices(" << program
<< ", " << count << ", " << names << ", " << indices << ")");
TRACE_EVENT0("gpu", "GLES2::GetUniformIndices");
if (count < 0) {
SetGLError(GL_INVALID_VALUE, "glGetUniformIndices", "count < 0");
return;
}
if (count == 0) {
return;
}
bool success = share_group_->program_info_manager()->GetUniformIndices(
this, program, count, names, indices);
if (success) {
GPU_CLIENT_LOG_CODE_BLOCK({
for (GLsizei ii = 0; ii < count; ++ii) {
GPU_CLIENT_LOG(" " << ii << ": " << indices[ii]);
}
});
}
CheckGLError();
}
bool GLES2Implementation::GetProgramivHelper(
GLuint program, GLenum pname, GLint* params) {
bool got_value = share_group_->program_info_manager()->GetProgramiv(
this, program, pname, params);
GPU_CLIENT_LOG_CODE_BLOCK({
if (got_value) {
GPU_CLIENT_LOG(" 0: " << *params);
}
});
return got_value;
}
GLint GLES2Implementation::GetFragDataLocationHelper(
GLuint program, const char* name) {
typedef cmds::GetFragDataLocation::Result Result;
Result* result = GetResultAs<Result*>();
if (!result) {
return -1;
}
*result = -1;
SetBucketAsCString(kResultBucketId, name);
helper_->GetFragDataLocation(
program, kResultBucketId, GetResultShmId(), GetResultShmOffset());
WaitForCmd();
helper_->SetBucketSize(kResultBucketId, 0);
return *result;
}
GLint GLES2Implementation::GetFragDataLocation(
GLuint program, const char* name) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glGetFragDataLocation("
<< program << ", " << name << ")");
TRACE_EVENT0("gpu", "GLES2::GetFragDataLocation");
GLint loc = share_group_->program_info_manager()->GetFragDataLocation(
this, program, name);
GPU_CLIENT_LOG("returned " << loc);
CheckGLError();
return loc;
}
GLuint GLES2Implementation::GetUniformBlockIndexHelper(
GLuint program, const char* name) {
typedef cmds::GetUniformBlockIndex::Result Result;
Result* result = GetResultAs<Result*>();
if (!result) {
return GL_INVALID_INDEX;
}
*result = GL_INVALID_INDEX;
SetBucketAsCString(kResultBucketId, name);
helper_->GetUniformBlockIndex(
program, kResultBucketId, GetResultShmId(), GetResultShmOffset());
WaitForCmd();
helper_->SetBucketSize(kResultBucketId, 0);
return *result;
}
GLuint GLES2Implementation::GetUniformBlockIndex(
GLuint program, const char* name) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glGetUniformBlockIndex("
<< program << ", " << name << ")");
TRACE_EVENT0("gpu", "GLES2::GetUniformBlockIndex");
GLuint index = share_group_->program_info_manager()->GetUniformBlockIndex(
this, program, name);
GPU_CLIENT_LOG("returned " << index);
CheckGLError();
return index;
}
void GLES2Implementation::LinkProgram(GLuint program) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glLinkProgram(" << program << ")");
helper_->LinkProgram(program);
share_group_->program_info_manager()->CreateInfo(program);
CheckGLError();
}
void GLES2Implementation::ShaderBinary(
GLsizei n, const GLuint* shaders, GLenum binaryformat, const void* binary,
GLsizei length) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glShaderBinary(" << n << ", "
<< static_cast<const void*>(shaders) << ", "
<< GLES2Util::GetStringEnum(binaryformat) << ", "
<< static_cast<const void*>(binary) << ", "
<< length << ")");
if (n < 0) {
SetGLError(GL_INVALID_VALUE, "glShaderBinary", "n < 0.");
return;
}
if (length < 0) {
SetGLError(GL_INVALID_VALUE, "glShaderBinary", "length < 0.");
return;
}
// TODO(gman): ShaderBinary should use buckets.
unsigned int shader_id_size = n * sizeof(*shaders);
ScopedTransferBufferArray<GLint> buffer(
shader_id_size + length, helper_, transfer_buffer_);
if (!buffer.valid() || buffer.num_elements() != shader_id_size + length) {
SetGLError(GL_OUT_OF_MEMORY, "glShaderBinary", "out of memory.");
return;
}
void* shader_ids = buffer.elements();
void* shader_data = buffer.elements() + shader_id_size;
memcpy(shader_ids, shaders, shader_id_size);
memcpy(shader_data, binary, length);
helper_->ShaderBinary(
n,
buffer.shm_id(),
buffer.offset(),
binaryformat,
buffer.shm_id(),
buffer.offset() + shader_id_size,
length);
CheckGLError();
}
void GLES2Implementation::PixelStorei(GLenum pname, GLint param) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glPixelStorei("
<< GLES2Util::GetStringPixelStore(pname) << ", "
<< param << ")");
switch (pname) {
case GL_PACK_ALIGNMENT:
pack_alignment_ = param;
break;
case GL_UNPACK_ALIGNMENT:
unpack_alignment_ = param;
break;
case GL_UNPACK_ROW_LENGTH_EXT:
unpack_row_length_ = param;
return;
case GL_UNPACK_IMAGE_HEIGHT:
unpack_image_height_ = param;
return;
case GL_UNPACK_SKIP_ROWS_EXT:
unpack_skip_rows_ = param;
return;
case GL_UNPACK_SKIP_PIXELS_EXT:
unpack_skip_pixels_ = param;
return;
case GL_UNPACK_SKIP_IMAGES:
unpack_skip_images_ = param;
return;
case GL_UNPACK_FLIP_Y_CHROMIUM:
unpack_flip_y_ = (param != 0);
break;
case GL_PACK_REVERSE_ROW_ORDER_ANGLE:
pack_reverse_row_order_ =
IsAnglePackReverseRowOrderAvailable() ? (param != 0) : false;
break;
default:
break;
}
helper_->PixelStorei(pname, param);
CheckGLError();
}
void GLES2Implementation::VertexAttribIPointer(
GLuint index, GLint size, GLenum type, GLsizei stride, const void* ptr) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glVertexAttribPointer("
<< index << ", "
<< size << ", "
<< GLES2Util::GetStringVertexAttribType(type) << ", "
<< stride << ", "
<< ptr << ")");
helper_->VertexAttribIPointer(index, size, type, stride, ToGLuint(ptr));
CheckGLError();
}
void GLES2Implementation::VertexAttribPointer(
GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride,
const void* ptr) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glVertexAttribPointer("
<< index << ", "
<< size << ", "
<< GLES2Util::GetStringVertexAttribType(type) << ", "
<< GLES2Util::GetStringBool(normalized) << ", "
<< stride << ", "
<< ptr << ")");
// Record the info on the client side.
if (!vertex_array_object_manager_->SetAttribPointer(
bound_array_buffer_id_, index, size, type, normalized, stride, ptr)) {
SetGLError(GL_INVALID_OPERATION, "glVertexAttribPointer",
"client side arrays are not allowed in vertex array objects.");
return;
}
if (!support_client_side_arrays_ || bound_array_buffer_id_ != 0) {
// Only report NON client side buffers to the service.
if (!ValidateOffset("glVertexAttribPointer",
reinterpret_cast<GLintptr>(ptr))) {
return;
}
helper_->VertexAttribPointer(index, size, type, normalized, stride,
ToGLuint(ptr));
}
CheckGLError();
}
void GLES2Implementation::VertexAttribDivisorANGLE(
GLuint index, GLuint divisor) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glVertexAttribDivisorANGLE("
<< index << ", "
<< divisor << ") ");
// Record the info on the client side.
vertex_array_object_manager_->SetAttribDivisor(index, divisor);
helper_->VertexAttribDivisorANGLE(index, divisor);
CheckGLError();
}
void GLES2Implementation::BufferDataHelper(
GLenum target, GLsizeiptr size, const void* data, GLenum usage) {
if (!ValidateSize("glBufferData", size))
return;
#if defined(MEMORY_SANITIZER) && !defined(OS_NACL)
// Do not upload uninitialized data. Even if it's not a bug, it can cause a
// bogus MSan report during a readback later. This is because MSan doesn't
// understand shared memory and would assume we were reading back the same
// unintialized data.
if (data) __msan_check_mem_is_initialized(data, size);
#endif
GLuint buffer_id;
if (GetBoundPixelTransferBuffer(target, "glBufferData", &buffer_id)) {
if (!buffer_id) {
return;
}
BufferTracker::Buffer* buffer = buffer_tracker_->GetBuffer(buffer_id);
if (buffer)
RemoveTransferBuffer(buffer);
// Create new buffer.
buffer = buffer_tracker_->CreateBuffer(buffer_id, size);
DCHECK(buffer);
if (buffer->address() && data)
memcpy(buffer->address(), data, size);
return;
}
RemoveMappedBufferRangeByTarget(target);
// If there is no data just send BufferData
if (size == 0 || !data) {
helper_->BufferData(target, size, 0, 0, usage);
return;
}
// See if we can send all at once.
ScopedTransferBufferPtr buffer(size, helper_, transfer_buffer_);
if (!buffer.valid()) {
return;
}
if (buffer.size() >= static_cast<unsigned int>(size)) {
memcpy(buffer.address(), data, size);
helper_->BufferData(
target,
size,
buffer.shm_id(),
buffer.offset(),
usage);
return;
}
// Make the buffer with BufferData then send via BufferSubData
helper_->BufferData(target, size, 0, 0, usage);
BufferSubDataHelperImpl(target, 0, size, data, &buffer);
CheckGLError();
}
void GLES2Implementation::BufferData(
GLenum target, GLsizeiptr size, const void* data, GLenum usage) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glBufferData("
<< GLES2Util::GetStringBufferTarget(target) << ", "
<< size << ", "
<< static_cast<const void*>(data) << ", "
<< GLES2Util::GetStringBufferUsage(usage) << ")");
BufferDataHelper(target, size, data, usage);
CheckGLError();
}
void GLES2Implementation::BufferSubDataHelper(
GLenum target, GLintptr offset, GLsizeiptr size, const void* data) {
if (size == 0) {
return;
}
if (!ValidateSize("glBufferSubData", size) ||
!ValidateOffset("glBufferSubData", offset)) {
return;
}
GLuint buffer_id;
if (GetBoundPixelTransferBuffer(target, "glBufferSubData", &buffer_id)) {
if (!buffer_id) {
return;
}
BufferTracker::Buffer* buffer = buffer_tracker_->GetBuffer(buffer_id);
if (!buffer) {
SetGLError(GL_INVALID_VALUE, "glBufferSubData", "unknown buffer");
return;
}
int32 end = 0;
int32 buffer_size = buffer->size();
if (!SafeAddInt32(offset, size, &end) || end > buffer_size) {
SetGLError(GL_INVALID_VALUE, "glBufferSubData", "out of range");
return;
}
if (buffer->address() && data)
memcpy(static_cast<uint8*>(buffer->address()) + offset, data, size);
return;
}
ScopedTransferBufferPtr buffer(size, helper_, transfer_buffer_);
BufferSubDataHelperImpl(target, offset, size, data, &buffer);
}
void GLES2Implementation::BufferSubDataHelperImpl(
GLenum target, GLintptr offset, GLsizeiptr size, const void* data,
ScopedTransferBufferPtr* buffer) {
DCHECK(buffer);
DCHECK_GT(size, 0);
const int8* source = static_cast<const int8*>(data);
while (size) {
if (!buffer->valid() || buffer->size() == 0) {
buffer->Reset(size);
if (!buffer->valid()) {
return;
}
}
memcpy(buffer->address(), source, buffer->size());
helper_->BufferSubData(
target, offset, buffer->size(), buffer->shm_id(), buffer->offset());
offset += buffer->size();
source += buffer->size();
size -= buffer->size();
buffer->Release();
}
}
void GLES2Implementation::BufferSubData(
GLenum target, GLintptr offset, GLsizeiptr size, const void* data) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glBufferSubData("
<< GLES2Util::GetStringBufferTarget(target) << ", "
<< offset << ", " << size << ", "
<< static_cast<const void*>(data) << ")");
BufferSubDataHelper(target, offset, size, data);
CheckGLError();
}
void GLES2Implementation::RemoveTransferBuffer(BufferTracker::Buffer* buffer) {
int32 token = buffer->last_usage_token();
uint32 async_token = buffer->last_async_upload_token();
if (async_token) {
if (HasAsyncUploadTokenPassed(async_token)) {
buffer_tracker_->Free(buffer);
} else {
detached_async_upload_memory_.push_back(
std::make_pair(buffer->address(), async_token));
buffer_tracker_->Unmanage(buffer);
}
} else if (token) {
if (helper_->HasTokenPassed(token))
buffer_tracker_->Free(buffer);
else
buffer_tracker_->FreePendingToken(buffer, token);
} else {
buffer_tracker_->Free(buffer);
}
buffer_tracker_->RemoveBuffer(buffer->id());
}
bool GLES2Implementation::GetBoundPixelTransferBuffer(
GLenum target,
const char* function_name,
GLuint* buffer_id) {
*buffer_id = 0;
switch (target) {
case GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM:
*buffer_id = bound_pixel_pack_transfer_buffer_id_;
break;
case GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM:
*buffer_id = bound_pixel_unpack_transfer_buffer_id_;
break;
default:
// Unknown target
return false;
}
if (!*buffer_id) {
SetGLError(GL_INVALID_OPERATION, function_name, "no buffer bound");
}
return true;
}
BufferTracker::Buffer*
GLES2Implementation::GetBoundPixelUnpackTransferBufferIfValid(
GLuint buffer_id,
const char* function_name,
GLuint offset, GLsizei size) {
DCHECK(buffer_id);
BufferTracker::Buffer* buffer = buffer_tracker_->GetBuffer(buffer_id);
if (!buffer) {
SetGLError(GL_INVALID_OPERATION, function_name, "invalid buffer");
return NULL;
}
if (buffer->mapped()) {
SetGLError(GL_INVALID_OPERATION, function_name, "buffer mapped");
return NULL;
}
if ((buffer->size() - offset) < static_cast<GLuint>(size)) {
SetGLError(GL_INVALID_VALUE, function_name, "unpack size to large");
return NULL;
}
return buffer;
}
void GLES2Implementation::CompressedTexImage2D(
GLenum target, GLint level, GLenum internalformat, GLsizei width,
GLsizei height, GLint border, GLsizei image_size, const void* data) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glCompressedTexImage2D("
<< GLES2Util::GetStringTextureTarget(target) << ", "
<< level << ", "
<< GLES2Util::GetStringCompressedTextureFormat(internalformat) << ", "
<< width << ", " << height << ", " << border << ", "
<< image_size << ", "
<< static_cast<const void*>(data) << ")");
if (width < 0 || height < 0 || level < 0) {
SetGLError(GL_INVALID_VALUE, "glCompressedTexImage2D", "dimension < 0");
return;
}
if (border != 0) {
SetGLError(GL_INVALID_VALUE, "glCompressedTexImage2D", "border != 0");
return;
}
if (height == 0 || width == 0) {
return;
}
// If there's a pixel unpack buffer bound use it when issuing
// CompressedTexImage2D.
if (bound_pixel_unpack_transfer_buffer_id_) {
GLuint offset = ToGLuint(data);
BufferTracker::Buffer* buffer = GetBoundPixelUnpackTransferBufferIfValid(
bound_pixel_unpack_transfer_buffer_id_,
"glCompressedTexImage2D", offset, image_size);
if (buffer && buffer->shm_id() != -1) {
helper_->CompressedTexImage2D(
target, level, internalformat, width, height, image_size,
buffer->shm_id(), buffer->shm_offset() + offset);
buffer->set_last_usage_token(helper_->InsertToken());
}
return;
}
SetBucketContents(kResultBucketId, data, image_size);
helper_->CompressedTexImage2DBucket(
target, level, internalformat, width, height, kResultBucketId);
// Free the bucket. This is not required but it does free up the memory.
// and we don't have to wait for the result so from the client's perspective
// it's cheap.
helper_->SetBucketSize(kResultBucketId, 0);
CheckGLError();
}
void GLES2Implementation::CompressedTexSubImage2D(
GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width,
GLsizei height, GLenum format, GLsizei image_size, const void* data) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glCompressedTexSubImage2D("
<< GLES2Util::GetStringTextureTarget(target) << ", "
<< level << ", "
<< xoffset << ", " << yoffset << ", "
<< width << ", " << height << ", "
<< GLES2Util::GetStringCompressedTextureFormat(format) << ", "
<< image_size << ", "
<< static_cast<const void*>(data) << ")");
if (width < 0 || height < 0 || level < 0) {
SetGLError(GL_INVALID_VALUE, "glCompressedTexSubImage2D", "dimension < 0");
return;
}
// If there's a pixel unpack buffer bound use it when issuing
// CompressedTexSubImage2D.
if (bound_pixel_unpack_transfer_buffer_id_) {
GLuint offset = ToGLuint(data);
BufferTracker::Buffer* buffer = GetBoundPixelUnpackTransferBufferIfValid(
bound_pixel_unpack_transfer_buffer_id_,
"glCompressedTexSubImage2D", offset, image_size);
if (buffer && buffer->shm_id() != -1) {
helper_->CompressedTexSubImage2D(
target, level, xoffset, yoffset, width, height, format, image_size,
buffer->shm_id(), buffer->shm_offset() + offset);
buffer->set_last_usage_token(helper_->InsertToken());
CheckGLError();
}
return;
}
SetBucketContents(kResultBucketId, data, image_size);
helper_->CompressedTexSubImage2DBucket(
target, level, xoffset, yoffset, width, height, format, kResultBucketId);
// Free the bucket. This is not required but it does free up the memory.
// and we don't have to wait for the result so from the client's perspective
// it's cheap.
helper_->SetBucketSize(kResultBucketId, 0);
CheckGLError();
}
namespace {
void CopyRectToBuffer(
const void* pixels,
uint32 height,
uint32 unpadded_row_size,
uint32 pixels_padded_row_size,
bool flip_y,
void* buffer,
uint32 buffer_padded_row_size) {
const int8* source = static_cast<const int8*>(pixels);
int8* dest = static_cast<int8*>(buffer);
if (flip_y || pixels_padded_row_size != buffer_padded_row_size) {
if (flip_y) {
dest += buffer_padded_row_size * (height - 1);
}
// the last row is copied unpadded at the end
for (; height > 1; --height) {
memcpy(dest, source, buffer_padded_row_size);
if (flip_y) {
dest -= buffer_padded_row_size;
} else {
dest += buffer_padded_row_size;
}
source += pixels_padded_row_size;
}
memcpy(dest, source, unpadded_row_size);
} else {
uint32 size = (height - 1) * pixels_padded_row_size + unpadded_row_size;
memcpy(dest, source, size);
}
}
} // anonymous namespace
void GLES2Implementation::TexImage2D(
GLenum target, GLint level, GLint internalformat, GLsizei width,
GLsizei height, GLint border, GLenum format, GLenum type,
const void* pixels) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glTexImage2D("
<< GLES2Util::GetStringTextureTarget(target) << ", "
<< level << ", "
<< GLES2Util::GetStringTextureInternalFormat(internalformat) << ", "
<< width << ", " << height << ", " << border << ", "
<< GLES2Util::GetStringTextureFormat(format) << ", "
<< GLES2Util::GetStringPixelType(type) << ", "
<< static_cast<const void*>(pixels) << ")");
if (level < 0 || height < 0 || width < 0) {
SetGLError(GL_INVALID_VALUE, "glTexImage2D", "dimension < 0");
return;
}
if (border != 0) {
SetGLError(GL_INVALID_VALUE, "glTexImage2D", "border != 0");
return;
}
uint32 size;
uint32 unpadded_row_size;
uint32 padded_row_size;
if (!GLES2Util::ComputeImageDataSizes(
width, height, 1, format, type, unpack_alignment_, &size,
&unpadded_row_size, &padded_row_size)) {
SetGLError(GL_INVALID_VALUE, "glTexImage2D", "image size too large");
return;
}
// If there's a pixel unpack buffer bound use it when issuing TexImage2D.
if (bound_pixel_unpack_transfer_buffer_id_) {
GLuint offset = ToGLuint(pixels);
BufferTracker::Buffer* buffer = GetBoundPixelUnpackTransferBufferIfValid(
bound_pixel_unpack_transfer_buffer_id_,
"glTexImage2D", offset, size);
if (buffer && buffer->shm_id() != -1) {
helper_->TexImage2D(
target, level, internalformat, width, height, format, type,
buffer->shm_id(), buffer->shm_offset() + offset);
buffer->set_last_usage_token(helper_->InsertToken());
CheckGLError();
}
return;
}
// If there's no data just issue TexImage2D
if (!pixels) {
helper_->TexImage2D(
target, level, internalformat, width, height, format, type,
0, 0);
CheckGLError();
return;
}
// compute the advance bytes per row for the src pixels
uint32 src_padded_row_size;
if (unpack_row_length_ > 0) {
if (!GLES2Util::ComputeImagePaddedRowSize(
unpack_row_length_, format, type, unpack_alignment_,
&src_padded_row_size)) {
SetGLError(
GL_INVALID_VALUE, "glTexImage2D", "unpack row length too large");
return;
}
} else {
src_padded_row_size = padded_row_size;
}
// advance pixels pointer past the skip rows and skip pixels
pixels = reinterpret_cast<const int8*>(pixels) +
unpack_skip_rows_ * src_padded_row_size;
if (unpack_skip_pixels_) {
uint32 group_size = GLES2Util::ComputeImageGroupSize(format, type);
pixels = reinterpret_cast<const int8*>(pixels) +
unpack_skip_pixels_ * group_size;
}
// Check if we can send it all at once.
ScopedTransferBufferPtr buffer(size, helper_, transfer_buffer_);
if (!buffer.valid()) {
return;
}
if (buffer.size() >= size) {
CopyRectToBuffer(
pixels, height, unpadded_row_size, src_padded_row_size, unpack_flip_y_,
buffer.address(), padded_row_size);
helper_->TexImage2D(
target, level, internalformat, width, height, format, type,
buffer.shm_id(), buffer.offset());
CheckGLError();
return;
}
// No, so send it using TexSubImage2D.
helper_->TexImage2D(
target, level, internalformat, width, height, format, type,
0, 0);
TexSubImage2DImpl(
target, level, 0, 0, width, height, format, type, unpadded_row_size,
pixels, src_padded_row_size, GL_TRUE, &buffer, padded_row_size);
CheckGLError();
}
void GLES2Implementation::TexImage3D(
GLenum target, GLint level, GLint internalformat, GLsizei width,
GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type,
const void* pixels) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glTexImage3D("
<< GLES2Util::GetStringTextureTarget(target) << ", "
<< level << ", "
<< GLES2Util::GetStringTextureInternalFormat(internalformat) << ", "
<< width << ", " << height << ", " << depth << ", " << border << ", "
<< GLES2Util::GetStringTextureFormat(format) << ", "
<< GLES2Util::GetStringPixelType(type) << ", "
<< static_cast<const void*>(pixels) << ")");
if (level < 0 || height < 0 || width < 0 || depth < 0) {
SetGLError(GL_INVALID_VALUE, "glTexImage3D", "dimension < 0");
return;
}
if (border != 0) {
SetGLError(GL_INVALID_VALUE, "glTexImage3D", "border != 0");
return;
}
uint32 size;
uint32 unpadded_row_size;
uint32 padded_row_size;
if (!GLES2Util::ComputeImageDataSizes(
width, height, depth, format, type, unpack_alignment_, &size,
&unpadded_row_size, &padded_row_size)) {
SetGLError(GL_INVALID_VALUE, "glTexImage3D", "image size too large");
return;
}
// If there's a pixel unpack buffer bound use it when issuing TexImage3D.
if (bound_pixel_unpack_transfer_buffer_id_) {
GLuint offset = ToGLuint(pixels);
BufferTracker::Buffer* buffer = GetBoundPixelUnpackTransferBufferIfValid(
bound_pixel_unpack_transfer_buffer_id_,
"glTexImage3D", offset, size);
if (buffer && buffer->shm_id() != -1) {
helper_->TexImage3D(
target, level, internalformat, width, height, depth, format, type,
buffer->shm_id(), buffer->shm_offset() + offset);
buffer->set_last_usage_token(helper_->InsertToken());
CheckGLError();
}
return;
}
// If there's no data just issue TexImage3D
if (!pixels) {
helper_->TexImage3D(
target, level, internalformat, width, height, depth, format, type,
0, 0);
CheckGLError();
return;
}
// compute the advance bytes per row for the src pixels
uint32 src_padded_row_size;
if (unpack_row_length_ > 0) {
if (!GLES2Util::ComputeImagePaddedRowSize(
unpack_row_length_, format, type, unpack_alignment_,
&src_padded_row_size)) {
SetGLError(
GL_INVALID_VALUE, "glTexImage3D", "unpack row length too large");
return;
}
} else {
src_padded_row_size = padded_row_size;
}
uint32 src_height = unpack_image_height_ > 0 ? unpack_image_height_ : height;
// advance pixels pointer past the skip images/rows/pixels
pixels = reinterpret_cast<const int8*>(pixels) +
unpack_skip_images_ * src_padded_row_size * src_height +
unpack_skip_rows_ * src_padded_row_size;
if (unpack_skip_pixels_) {
uint32 group_size = GLES2Util::ComputeImageGroupSize(format, type);
pixels = reinterpret_cast<const int8*>(pixels) +
unpack_skip_pixels_ * group_size;
}
// Check if we can send it all at once.
ScopedTransferBufferPtr buffer(size, helper_, transfer_buffer_);
if (!buffer.valid()) {
return;
}
if (buffer.size() >= size) {
void* buffer_pointer = buffer.address();
for (GLsizei z = 0; z < depth; ++z) {
// Only the last row of the last image is unpadded.
uint32 src_unpadded_row_size =
(z == depth - 1) ? unpadded_row_size : src_padded_row_size;
// TODO(zmo): Ignore flip_y flag for now.
CopyRectToBuffer(
pixels, height, src_unpadded_row_size, src_padded_row_size, false,
buffer_pointer, padded_row_size);
pixels = reinterpret_cast<const int8*>(pixels) +
src_padded_row_size * src_height;
buffer_pointer = reinterpret_cast<int8*>(buffer_pointer) +
padded_row_size * height;
}
helper_->TexImage3D(
target, level, internalformat, width, height, depth, format, type,
buffer.shm_id(), buffer.offset());
CheckGLError();
return;
}
// No, so send it using TexSubImage3D.
helper_->TexImage3D(
target, level, internalformat, width, height, depth, format, type,
0, 0);
TexSubImage3DImpl(
target, level, 0, 0, 0, width, height, depth, format, type,
unpadded_row_size, pixels, src_padded_row_size, GL_TRUE, &buffer,
padded_row_size);
CheckGLError();
}
void GLES2Implementation::TexSubImage2D(
GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width,
GLsizei height, GLenum format, GLenum type, const void* pixels) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glTexSubImage2D("
<< GLES2Util::GetStringTextureTarget(target) << ", "
<< level << ", "
<< xoffset << ", " << yoffset << ", "
<< width << ", " << height << ", "
<< GLES2Util::GetStringTextureFormat(format) << ", "
<< GLES2Util::GetStringPixelType(type) << ", "
<< static_cast<const void*>(pixels) << ")");
if (level < 0 || height < 0 || width < 0) {
SetGLError(GL_INVALID_VALUE, "glTexSubImage2D", "dimension < 0");
return;
}
if (height == 0 || width == 0) {
return;
}
uint32 temp_size;
uint32 unpadded_row_size;
uint32 padded_row_size;
if (!GLES2Util::ComputeImageDataSizes(
width, height, 1, format, type, unpack_alignment_, &temp_size,
&unpadded_row_size, &padded_row_size)) {
SetGLError(GL_INVALID_VALUE, "glTexSubImage2D", "size to large");
return;
}
// If there's a pixel unpack buffer bound use it when issuing TexSubImage2D.
if (bound_pixel_unpack_transfer_buffer_id_) {
GLuint offset = ToGLuint(pixels);
BufferTracker::Buffer* buffer = GetBoundPixelUnpackTransferBufferIfValid(
bound_pixel_unpack_transfer_buffer_id_,
"glTexSubImage2D", offset, temp_size);
if (buffer && buffer->shm_id() != -1) {
helper_->TexSubImage2D(
target, level, xoffset, yoffset, width, height, format, type,
buffer->shm_id(), buffer->shm_offset() + offset, false);
buffer->set_last_usage_token(helper_->InsertToken());
CheckGLError();
}
return;
}
// compute the advance bytes per row for the src pixels
uint32 src_padded_row_size;
if (unpack_row_length_ > 0) {
if (!GLES2Util::ComputeImagePaddedRowSize(
unpack_row_length_, format, type, unpack_alignment_,
&src_padded_row_size)) {
SetGLError(
GL_INVALID_VALUE, "glTexImage2D", "unpack row length too large");
return;
}
} else {
src_padded_row_size = padded_row_size;
}
// advance pixels pointer past the skip rows and skip pixels
pixels = reinterpret_cast<const int8*>(pixels) +
unpack_skip_rows_ * src_padded_row_size;
if (unpack_skip_pixels_) {
uint32 group_size = GLES2Util::ComputeImageGroupSize(format, type);
pixels = reinterpret_cast<const int8*>(pixels) +
unpack_skip_pixels_ * group_size;
}
ScopedTransferBufferPtr buffer(temp_size, helper_, transfer_buffer_);
TexSubImage2DImpl(
target, level, xoffset, yoffset, width, height, format, type,
unpadded_row_size, pixels, src_padded_row_size, GL_FALSE, &buffer,
padded_row_size);
CheckGLError();
}
void GLES2Implementation::TexSubImage3D(
GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset,
GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type,
const void* pixels) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glTexSubImage3D("
<< GLES2Util::GetStringTextureTarget(target) << ", "
<< level << ", "
<< xoffset << ", " << yoffset << ", " << zoffset << ", "
<< width << ", " << height << ", " << depth << ", "
<< GLES2Util::GetStringTextureFormat(format) << ", "
<< GLES2Util::GetStringPixelType(type) << ", "
<< static_cast<const void*>(pixels) << ")");
if (level < 0 || height < 0 || width < 0 || depth < 0) {
SetGLError(GL_INVALID_VALUE, "glTexSubImage3D", "dimension < 0");
return;
}
if (height == 0 || width == 0 || depth == 0) {
return;
}
uint32 temp_size;
uint32 unpadded_row_size;
uint32 padded_row_size;
if (!GLES2Util::ComputeImageDataSizes(
width, height, depth, format, type, unpack_alignment_, &temp_size,
&unpadded_row_size, &padded_row_size)) {
SetGLError(GL_INVALID_VALUE, "glTexSubImage3D", "size to large");
return;
}
// If there's a pixel unpack buffer bound use it when issuing TexSubImage2D.
if (bound_pixel_unpack_transfer_buffer_id_) {
GLuint offset = ToGLuint(pixels);
BufferTracker::Buffer* buffer = GetBoundPixelUnpackTransferBufferIfValid(
bound_pixel_unpack_transfer_buffer_id_,
"glTexSubImage3D", offset, temp_size);
if (buffer && buffer->shm_id() != -1) {
helper_->TexSubImage3D(
target, level, xoffset, yoffset, zoffset, width, height, depth,
format, type, buffer->shm_id(), buffer->shm_offset() + offset, false);
buffer->set_last_usage_token(helper_->InsertToken());
CheckGLError();
}
return;
}
// compute the advance bytes per row for the src pixels
uint32 src_padded_row_size;
if (unpack_row_length_ > 0) {
if (!GLES2Util::ComputeImagePaddedRowSize(
unpack_row_length_, format, type, unpack_alignment_,
&src_padded_row_size)) {
SetGLError(
GL_INVALID_VALUE, "glTexImage3D", "unpack row length too large");
return;
}
} else {
src_padded_row_size = padded_row_size;
}
uint32 src_height = unpack_image_height_ > 0 ? unpack_image_height_ : height;
// advance pixels pointer past the skip images/rows/pixels
pixels = reinterpret_cast<const int8*>(pixels) +
unpack_skip_images_ * src_padded_row_size * src_height +
unpack_skip_rows_ * src_padded_row_size;
if (unpack_skip_pixels_) {
uint32 group_size = GLES2Util::ComputeImageGroupSize(format, type);
pixels = reinterpret_cast<const int8*>(pixels) +
unpack_skip_pixels_ * group_size;
}
ScopedTransferBufferPtr buffer(temp_size, helper_, transfer_buffer_);
TexSubImage3DImpl(
target, level, xoffset, yoffset, zoffset, width, height, depth,
format, type, unpadded_row_size, pixels, src_padded_row_size, GL_FALSE,
&buffer, padded_row_size);
CheckGLError();
}
static GLint ComputeNumRowsThatFitInBuffer(
uint32 padded_row_size, uint32 unpadded_row_size,
unsigned int size, GLsizei remaining_rows) {
DCHECK_GE(unpadded_row_size, 0u);
if (padded_row_size == 0) {
return 1;
}
GLint num_rows = size / padded_row_size;
if (num_rows + 1 == remaining_rows &&
size - num_rows * padded_row_size >= unpadded_row_size) {
num_rows++;
}
return num_rows;
}
void GLES2Implementation::TexSubImage2DImpl(
GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width,
GLsizei height, GLenum format, GLenum type, uint32 unpadded_row_size,
const void* pixels, uint32 pixels_padded_row_size, GLboolean internal,
ScopedTransferBufferPtr* buffer, uint32 buffer_padded_row_size) {
DCHECK(buffer);
DCHECK_GE(level, 0);
DCHECK_GT(height, 0);
DCHECK_GT(width, 0);
const int8* source = reinterpret_cast<const int8*>(pixels);
GLint original_yoffset = yoffset;
// Transfer by rows.
while (height) {
unsigned int desired_size =
buffer_padded_row_size * (height - 1) + unpadded_row_size;
if (!buffer->valid() || buffer->size() == 0) {
buffer->Reset(desired_size);
if (!buffer->valid()) {
return;
}
}
GLint num_rows = ComputeNumRowsThatFitInBuffer(
buffer_padded_row_size, unpadded_row_size, buffer->size(), height);
num_rows = std::min(num_rows, height);
CopyRectToBuffer(
source, num_rows, unpadded_row_size, pixels_padded_row_size,
unpack_flip_y_, buffer->address(), buffer_padded_row_size);
GLint y = unpack_flip_y_ ? original_yoffset + height - num_rows : yoffset;
helper_->TexSubImage2D(
target, level, xoffset, y, width, num_rows, format, type,
buffer->shm_id(), buffer->offset(), internal);
buffer->Release();
yoffset += num_rows;
source += num_rows * pixels_padded_row_size;
height -= num_rows;
}
}
void GLES2Implementation::TexSubImage3DImpl(
GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei zoffset,
GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type,
uint32 unpadded_row_size, const void* pixels, uint32 pixels_padded_row_size,
GLboolean internal, ScopedTransferBufferPtr* buffer,
uint32 buffer_padded_row_size) {
DCHECK(buffer);
DCHECK_GE(level, 0);
DCHECK_GT(height, 0);
DCHECK_GT(width, 0);
DCHECK_GT(depth, 0);
const int8* source = reinterpret_cast<const int8*>(pixels);
GLsizei total_rows = height * depth;
GLint row_index = 0, depth_index = 0;
while (total_rows) {
// Each time, we either copy one or more images, or copy one or more rows
// within a single image, depending on the buffer size limit.
GLsizei max_rows;
unsigned int desired_size;
if (row_index > 0) {
// We are in the middle of an image. Send the remaining of the image.
max_rows = height - row_index;
if (total_rows <= height) {
// Last image, so last row is unpadded.
desired_size = buffer_padded_row_size * (max_rows - 1) +
unpadded_row_size;
} else {
desired_size = buffer_padded_row_size * max_rows;
}
} else {
// Send all the remaining data if possible.
max_rows = total_rows;
desired_size =
buffer_padded_row_size * (max_rows - 1) + unpadded_row_size;
}
if (!buffer->valid() || buffer->size() == 0) {
buffer->Reset(desired_size);
if (!buffer->valid()) {
return;
}
}
GLint num_rows = ComputeNumRowsThatFitInBuffer(
buffer_padded_row_size, unpadded_row_size, buffer->size(), total_rows);
num_rows = std::min(num_rows, max_rows);
GLint num_images = num_rows / height;
GLsizei my_height, my_depth;
if (num_images > 0) {
num_rows = num_images * height;
my_height = height;
my_depth = num_images;
} else {
my_height = num_rows;
my_depth = 1;
}
// TODO(zmo): Ignore flip_y flag for now.
if (num_images > 0) {
int8* buffer_pointer = reinterpret_cast<int8*>(buffer->address());
uint32 src_height =
unpack_image_height_ > 0 ? unpack_image_height_ : height;
uint32 image_size_dst = buffer_padded_row_size * height;
uint32 image_size_src = pixels_padded_row_size * src_height;
for (GLint ii = 0; ii < num_images; ++ii) {
uint32 my_unpadded_row_size;
if (total_rows == num_rows && ii + 1 == num_images)
my_unpadded_row_size = unpadded_row_size;
else
my_unpadded_row_size = pixels_padded_row_size;
CopyRectToBuffer(
source + ii * image_size_src, my_height, my_unpadded_row_size,
pixels_padded_row_size, false, buffer_pointer + ii * image_size_dst,
buffer_padded_row_size);
}
} else {
uint32 my_unpadded_row_size;
if (total_rows == num_rows)
my_unpadded_row_size = unpadded_row_size;
else
my_unpadded_row_size = pixels_padded_row_size;
CopyRectToBuffer(
source, my_height, my_unpadded_row_size, pixels_padded_row_size,
false, buffer->address(), buffer_padded_row_size);
}
helper_->TexSubImage3D(
target, level, xoffset, yoffset + row_index, zoffset + depth_index,
width, my_height, my_depth,
format, type, buffer->shm_id(), buffer->offset(), internal);
buffer->Release();
total_rows -= num_rows;
if (total_rows > 0) {
GLint num_image_paddings;
if (num_images > 0) {
DCHECK_EQ(row_index, 0);
depth_index += num_images;
num_image_paddings = num_images;
} else {
row_index = (row_index + my_height) % height;
num_image_paddings = 0;
if (my_height > 0 && row_index == 0) {
depth_index++;
num_image_paddings++;
}
}
source += num_rows * pixels_padded_row_size;
if (unpack_image_height_ > height && num_image_paddings > 0) {
source += num_image_paddings * (unpack_image_height_ - height) *
pixels_padded_row_size;
}
}
}
}
bool GLES2Implementation::GetActiveAttribHelper(
GLuint program, GLuint index, GLsizei bufsize, GLsizei* length, GLint* size,
GLenum* type, char* name) {
// Clear the bucket so if the command fails nothing will be in it.
helper_->SetBucketSize(kResultBucketId, 0);
typedef cmds::GetActiveAttrib::Result Result;
Result* result = GetResultAs<Result*>();
if (!result) {
return false;
}
// Set as failed so if the command fails we'll recover.
result->success = false;
helper_->GetActiveAttrib(program, index, kResultBucketId,
GetResultShmId(), GetResultShmOffset());
WaitForCmd();
if (result->success) {
if (size) {
*size = result->size;
}
if (type) {
*type = result->type;
}
if (length || name) {
std::vector<int8> str;
GetBucketContents(kResultBucketId, &str);
GLsizei max_size = std::min(static_cast<size_t>(bufsize) - 1,
std::max(static_cast<size_t>(0),
str.size() - 1));
if (length) {
*length = max_size;
}
if (name && bufsize > 0) {
memcpy(name, &str[0], max_size);
name[max_size] = '\0';
}
}
}
return result->success != 0;
}
void GLES2Implementation::GetActiveAttrib(
GLuint program, GLuint index, GLsizei bufsize, GLsizei* length, GLint* size,
GLenum* type, char* name) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glGetActiveAttrib("
<< program << ", " << index << ", " << bufsize << ", "
<< static_cast<const void*>(length) << ", "
<< static_cast<const void*>(size) << ", "
<< static_cast<const void*>(type) << ", "
<< static_cast<const void*>(name) << ", ");
if (bufsize < 0) {
SetGLError(GL_INVALID_VALUE, "glGetActiveAttrib", "bufsize < 0");
return;
}
TRACE_EVENT0("gpu", "GLES2::GetActiveAttrib");
bool success = share_group_->program_info_manager()->GetActiveAttrib(
this, program, index, bufsize, length, size, type, name);
if (success) {
if (size) {
GPU_CLIENT_LOG(" size: " << *size);
}
if (type) {
GPU_CLIENT_LOG(" type: " << GLES2Util::GetStringEnum(*type));
}
if (name) {
GPU_CLIENT_LOG(" name: " << name);
}
}
CheckGLError();
}
bool GLES2Implementation::GetActiveUniformHelper(
GLuint program, GLuint index, GLsizei bufsize, GLsizei* length, GLint* size,
GLenum* type, char* name) {
// Clear the bucket so if the command fails nothing will be in it.
helper_->SetBucketSize(kResultBucketId, 0);
typedef cmds::GetActiveUniform::Result Result;
Result* result = GetResultAs<Result*>();
if (!result) {
return false;
}
// Set as failed so if the command fails we'll recover.
result->success = false;
helper_->GetActiveUniform(program, index, kResultBucketId,
GetResultShmId(), GetResultShmOffset());
WaitForCmd();
if (result->success) {
if (size) {
*size = result->size;
}
if (type) {
*type = result->type;
}
if (length || name) {
std::vector<int8> str;
GetBucketContents(kResultBucketId, &str);
GLsizei max_size = std::min(static_cast<size_t>(bufsize) - 1,
std::max(static_cast<size_t>(0),
str.size() - 1));
if (length) {
*length = max_size;
}
if (name && bufsize > 0) {
memcpy(name, &str[0], max_size);
name[max_size] = '\0';
}
}
}
return result->success != 0;
}
void GLES2Implementation::GetActiveUniform(
GLuint program, GLuint index, GLsizei bufsize, GLsizei* length, GLint* size,
GLenum* type, char* name) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glGetActiveUniform("
<< program << ", " << index << ", " << bufsize << ", "
<< static_cast<const void*>(length) << ", "
<< static_cast<const void*>(size) << ", "
<< static_cast<const void*>(type) << ", "
<< static_cast<const void*>(name) << ", ");
if (bufsize < 0) {
SetGLError(GL_INVALID_VALUE, "glGetActiveUniform", "bufsize < 0");
return;
}
TRACE_EVENT0("gpu", "GLES2::GetActiveUniform");
bool success = share_group_->program_info_manager()->GetActiveUniform(
this, program, index, bufsize, length, size, type, name);
if (success) {
if (size) {
GPU_CLIENT_LOG(" size: " << *size);
}
if (type) {
GPU_CLIENT_LOG(" type: " << GLES2Util::GetStringEnum(*type));
}
if (name) {
GPU_CLIENT_LOG(" name: " << name);
}
}
CheckGLError();
}
bool GLES2Implementation::GetActiveUniformBlockNameHelper(
GLuint program, GLuint index, GLsizei bufsize,
GLsizei* length, char* name) {
DCHECK_LE(0, bufsize);
// Clear the bucket so if the command fails nothing will be in it.
helper_->SetBucketSize(kResultBucketId, 0);
typedef cmds::GetActiveUniformBlockName::Result Result;
Result* result = GetResultAs<Result*>();
if (!result) {
return false;
}
// Set as failed so if the command fails we'll recover.
*result = 0;
helper_->GetActiveUniformBlockName(program, index, kResultBucketId,
GetResultShmId(), GetResultShmOffset());
WaitForCmd();
if (*result) {
if (bufsize == 0) {
if (length) {
*length = 0;
}
} else if (length || name) {
std::vector<int8> str;
GetBucketContents(kResultBucketId, &str);
DCHECK(str.size() > 0);
GLsizei max_size =
std::min(bufsize, static_cast<GLsizei>(str.size())) - 1;
if (length) {
*length = max_size;
}
if (name) {
memcpy(name, &str[0], max_size);
name[max_size] = '\0';
}
}
}
return *result != 0;
}
void GLES2Implementation::GetActiveUniformBlockName(
GLuint program, GLuint index, GLsizei bufsize,
GLsizei* length, char* name) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glGetActiveUniformBlockName("
<< program << ", " << index << ", " << bufsize << ", "
<< static_cast<const void*>(length) << ", "
<< static_cast<const void*>(name) << ")");
if (bufsize < 0) {
SetGLError(GL_INVALID_VALUE, "glGetActiveUniformBlockName", "bufsize < 0");
return;
}
TRACE_EVENT0("gpu", "GLES2::GetActiveUniformBlockName");
bool success =
share_group_->program_info_manager()->GetActiveUniformBlockName(
this, program, index, bufsize, length, name);
if (success) {
if (name) {
GPU_CLIENT_LOG(" name: " << name);
}
}
CheckGLError();
}
bool GLES2Implementation::GetActiveUniformBlockivHelper(
GLuint program, GLuint index, GLenum pname, GLint* params) {
typedef cmds::GetActiveUniformBlockiv::Result Result;
Result* result = GetResultAs<Result*>();
if (!result) {
return false;
}
result->SetNumResults(0);
helper_->GetActiveUniformBlockiv(
program, index, pname, GetResultShmId(), GetResultShmOffset());
WaitForCmd();
if (result->GetNumResults() > 0) {
if (params) {
result->CopyResult(params);
}
GPU_CLIENT_LOG_CODE_BLOCK({
for (int32_t i = 0; i < result->GetNumResults(); ++i) {
GPU_CLIENT_LOG(" " << i << ": " << result->GetData()[i]);
}
});
return true;
}
return false;
}
void GLES2Implementation::GetActiveUniformBlockiv(
GLuint program, GLuint index, GLenum pname, GLint* params) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glGetActiveUniformBlockiv("
<< program << ", " << index << ", "
<< GLES2Util::GetStringUniformBlockParameter(pname) << ", "
<< static_cast<const void*>(params) << ")");
TRACE_EVENT0("gpu", "GLES2::GetActiveUniformBlockiv");
bool success =
share_group_->program_info_manager()->GetActiveUniformBlockiv(
this, program, index, pname, params);
if (success) {
if (params) {
// TODO(zmo): For GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, there will
// be more than one value returned in params.
GPU_CLIENT_LOG(" params: " << params[0]);
}
}
CheckGLError();
}
bool GLES2Implementation::GetActiveUniformsivHelper(
GLuint program, GLsizei count, const GLuint* indices,
GLenum pname, GLint* params) {
typedef cmds::GetActiveUniformsiv::Result Result;
Result* result = GetResultAs<Result*>();
if (!result) {
return false;
}
result->SetNumResults(0);
base::CheckedNumeric<size_t> bytes = static_cast<size_t>(count);
bytes *= sizeof(GLuint);
if (!bytes.IsValid()) {
SetGLError(GL_INVALID_VALUE, "glGetActiveUniformsiv", "count overflow");
return false;
}
SetBucketContents(kResultBucketId, indices, bytes.ValueOrDefault(0));
helper_->GetActiveUniformsiv(
program, kResultBucketId, pname, GetResultShmId(), GetResultShmOffset());
WaitForCmd();
bool success = result->GetNumResults() == count;
if (success) {
if (params) {
result->CopyResult(params);
}
GPU_CLIENT_LOG_CODE_BLOCK({
for (int32_t i = 0; i < result->GetNumResults(); ++i) {
GPU_CLIENT_LOG(" " << i << ": " << result->GetData()[i]);
}
});
}
helper_->SetBucketSize(kResultBucketId, 0);
return success;
}
void GLES2Implementation::GetActiveUniformsiv(
GLuint program, GLsizei count, const GLuint* indices,
GLenum pname, GLint* params) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glGetActiveUniformsiv("
<< program << ", " << count << ", "
<< static_cast<const void*>(indices) << ", "
<< GLES2Util::GetStringUniformParameter(pname) << ", "
<< static_cast<const void*>(params) << ")");
TRACE_EVENT0("gpu", "GLES2::GetActiveUniformsiv");
if (count < 0) {
SetGLError(GL_INVALID_VALUE, "glGetActiveUniformsiv", "count < 0");
return;
}
bool success = share_group_->program_info_manager()->GetActiveUniformsiv(
this, program, count, indices, pname, params);
if (success) {
if (params) {
GPU_CLIENT_LOG_CODE_BLOCK({
for (GLsizei ii = 0; ii < count; ++ii) {
GPU_CLIENT_LOG(" " << ii << ": " << params[ii]);
}
});
}
}
CheckGLError();
}
void GLES2Implementation::GetAttachedShaders(
GLuint program, GLsizei maxcount, GLsizei* count, GLuint* shaders) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glGetAttachedShaders("
<< program << ", " << maxcount << ", "
<< static_cast<const void*>(count) << ", "
<< static_cast<const void*>(shaders) << ", ");
if (maxcount < 0) {
SetGLError(GL_INVALID_VALUE, "glGetAttachedShaders", "maxcount < 0");
return;
}
TRACE_EVENT0("gpu", "GLES2::GetAttachedShaders");
typedef cmds::GetAttachedShaders::Result Result;
uint32 size = Result::ComputeSize(maxcount);
Result* result = static_cast<Result*>(transfer_buffer_->Alloc(size));
if (!result) {
return;
}
result->SetNumResults(0);
helper_->GetAttachedShaders(
program,
transfer_buffer_->GetShmId(),
transfer_buffer_->GetOffset(result),
size);
int32 token = helper_->InsertToken();
WaitForCmd();
if (count) {
*count = result->GetNumResults();
}
result->CopyResult(shaders);
GPU_CLIENT_LOG_CODE_BLOCK({
for (int32 i = 0; i < result->GetNumResults(); ++i) {
GPU_CLIENT_LOG(" " << i << ": " << result->GetData()[i]);
}
});
transfer_buffer_->FreePendingToken(result, token);
CheckGLError();
}
void GLES2Implementation::GetShaderPrecisionFormat(
GLenum shadertype, GLenum precisiontype, GLint* range, GLint* precision) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glGetShaderPrecisionFormat("
<< GLES2Util::GetStringShaderType(shadertype) << ", "
<< GLES2Util::GetStringShaderPrecision(precisiontype) << ", "
<< static_cast<const void*>(range) << ", "
<< static_cast<const void*>(precision) << ", ");
TRACE_EVENT0("gpu", "GLES2::GetShaderPrecisionFormat");
typedef cmds::GetShaderPrecisionFormat::Result Result;
Result* result = GetResultAs<Result*>();
if (!result) {
return;
}
GLStaticState::ShaderPrecisionKey key(shadertype, precisiontype);
GLStaticState::ShaderPrecisionMap::iterator i =
static_state_.shader_precisions.find(key);
if (i != static_state_.shader_precisions.end()) {
*result = i->second;
} else {
result->success = false;
helper_->GetShaderPrecisionFormat(
shadertype, precisiontype, GetResultShmId(), GetResultShmOffset());
WaitForCmd();
if (result->success)
static_state_.shader_precisions[key] = *result;
}
if (result->success) {
if (range) {
range[0] = result->min_range;
range[1] = result->max_range;
GPU_CLIENT_LOG(" min_range: " << range[0]);
GPU_CLIENT_LOG(" min_range: " << range[1]);
}
if (precision) {
precision[0] = result->precision;
GPU_CLIENT_LOG(" min_range: " << precision[0]);
}
}
CheckGLError();
}
const GLubyte* GLES2Implementation::GetStringHelper(GLenum name) {
const char* result = NULL;
// Clears the bucket so if the command fails nothing will be in it.
helper_->SetBucketSize(kResultBucketId, 0);
helper_->GetString(name, kResultBucketId);
std::string str;
if (GetBucketAsString(kResultBucketId, &str)) {
// Adds extensions implemented on client side only.
switch (name) {
case GL_EXTENSIONS:
str += std::string(str.empty() ? "" : " ") +
"GL_CHROMIUM_flipy "
"GL_EXT_unpack_subimage "
"GL_CHROMIUM_map_sub";
if (capabilities_.image)
str += " GL_CHROMIUM_image GL_CHROMIUM_gpu_memory_buffer_image";
if (capabilities_.future_sync_points)
str += " GL_CHROMIUM_future_sync_point";
break;
default:
break;
}
// Because of WebGL the extensions can change. We have to cache each unique
// result since we don't know when the client will stop referring to a
// previous one it queries.
GLStringMap::iterator it = gl_strings_.find(name);
if (it == gl_strings_.end()) {
std::set<std::string> strings;
std::pair<GLStringMap::iterator, bool> insert_result =
gl_strings_.insert(std::make_pair(name, strings));
DCHECK(insert_result.second);
it = insert_result.first;
}
std::set<std::string>& string_set = it->second;
std::set<std::string>::const_iterator sit = string_set.find(str);
if (sit != string_set.end()) {
result = sit->c_str();
} else {
std::pair<std::set<std::string>::const_iterator, bool> insert_result =
string_set.insert(str);
DCHECK(insert_result.second);
result = insert_result.first->c_str();
}
}
return reinterpret_cast<const GLubyte*>(result);
}
const GLubyte* GLES2Implementation::GetString(GLenum name) {
GPU_CLIENT_SINGLE_THREAD_CHECK();
GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glGetString("
<< GLES2Util::GetStringStringType(name) << ")");
TRACE_EVENT0("gpu", "GLES2::GetString");
const GLubyte* result = GetStringHelper(name);
GPU_CLIENT_LOG(" returned " << reinterpret_cast<const char*>(result));
CheckGLError();
return result;
}
bool GLES2Implementation::GetTransformFeedbackVaryingHelper(
GLuint program, GLuint index, GLsizei bufsize, GLsizei* length, GLint* size,
GLenum* type, char* name) {
// Clear the bucket so if the command fails nothing will be in it.
helper_->SetBucketSize(kResultBucketId, 0);
typedef cmds::GetTransformFeedbackVarying::Result Result;
Result* result = GetResultAs<Result*>();
if (!result) {
return false;
}
// Set as failed so if the command fails we'll recover.
result->success = false;
helper_->GetTransformFeedbackVarying(
program, index, kResultBucketId, GetResultShmId(), GetResultShmOffset());
WaitForCmd();
if (result->success) {
if (size) {
*size = result->size;
}
if (type) {
*type = result->type;
}
if (length || name) {