diff options
author | gspencer@google.com <gspencer@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-27 23:15:42 +0000 |
---|---|---|
committer | gspencer@google.com <gspencer@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-27 23:15:42 +0000 |
commit | 05b47f7a8c5451f858dc220df0e3a97542edace6 (patch) | |
tree | a2273d619f0625c9d44d40842845ccce2eac1045 /o3d/command_buffer/client | |
parent | 5cdc8bdb4c847cefe7f4542bd10c9880c2c557a0 (diff) | |
download | chromium_src-05b47f7a8c5451f858dc220df0e3a97542edace6.zip chromium_src-05b47f7a8c5451f858dc220df0e3a97542edace6.tar.gz chromium_src-05b47f7a8c5451f858dc220df0e3a97542edace6.tar.bz2 |
This is the O3D source tree's initial commit to the Chromium tree. It
is not built or referenced at all by the chrome build yet, and doesn't
yet build in it's new home. We'll change that shortly.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@17035 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'o3d/command_buffer/client')
16 files changed, 3195 insertions, 0 deletions
diff --git a/o3d/command_buffer/client/build.scons b/o3d/command_buffer/client/build.scons new file mode 100644 index 0000000..eeefca6 --- /dev/null +++ b/o3d/command_buffer/client/build.scons @@ -0,0 +1,70 @@ +# Copyright 2009, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +Import('env') + +INPUTS = [ + 'cross/buffer_sync_proxy.cc', + 'cross/cmd_buffer_helper.cc', + 'cross/effect_helper.cc', + 'cross/fenced_allocator.cc', + 'cross/id_allocator.cc', +] + +# Create a target library from the sources called 'o3dCmdBuf' +o3dcmdbuf_lib = env.ComponentLibrary('o3dCmdBuf_client', INPUTS) + +# Add some flags and libraries to the build environment for the unit tests +env.Append( + LIBS = [ + 'o3dCmdBuf_client', + 'o3dCmdBuf_common', + ] + env['NACL_HTP_LIBS'], + LIBPATH = ['$NACL_LIB_DIR'], +) + +if env['TARGET_PLATFORM'] == 'WINDOWS': + env.Append(CCFLAGS = ['/Wp64'], + LINKFLAGS=['/SUBSYSTEM:CONSOLE']) + +if env['TARGET_PLATFORM'] != 'NACL': + env.Append(LIBS = ['o3d_base'] + env['ICU_LIBS']) + +# Build the big tests +BIG_TEST_CLIENT = [ + 'cross/big_test_client.cc', +] + +# Create a target executable program called 'o3dCmdBuf_bigtest_client' from the +# list of big test client files. +big_test_client = env.Program('o3dCmdBuf_bigtest_client', BIG_TEST_CLIENT) + +# Copy the resulting executable to the Artifacts directory. +env.Replicate('$ARTIFACTS_DIR', big_test_client) diff --git a/o3d/command_buffer/client/cross/big_test_client.cc b/o3d/command_buffer/client/cross/big_test_client.cc new file mode 100644 index 0000000..f829187 --- /dev/null +++ b/o3d/command_buffer/client/cross/big_test_client.cc @@ -0,0 +1,407 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#include <math.h> +#ifdef __native_client__ +#include <sys/nacl_syscalls.h> +#else +#include "native_client/service_runtime/nrd_xfer_lib/nrd_all_modules.h" +#endif +#include "command_buffer/common/cross/gapi_interface.h" +#include "command_buffer/common/cross/rpc_imc.h" +#include "command_buffer/client/cross/cmd_buffer_helper.h" +#include "command_buffer/client/cross/buffer_sync_proxy.h" +#include "third_party/vectormath/files/vectormathlibrary/include/vectormath/scalar/cpp/vectormath_aos.h" // NOLINT + +namespace o3d { +namespace command_buffer { + +namespace math = Vectormath::Aos; + +// Adds a Clear command into the command buffer. +// Parameters: +// cmd_buffer: the command buffer helper. +// buffers: a bitfield of which buffers to clear (a combination of +// GAPIInterface::COLOR, GAPIInterface::DEPTH and GAPIInterface::STENCIL). +// color: the color buffer clear value. +// depth: the depth buffer clear value. +// stencil: the stencil buffer clear value. +void ClearCmd(CommandBufferHelper *cmd_buffer, + const unsigned int buffers, + const RGBA &color, + float depth, + unsigned int stencil) { + CommandBufferEntry args[7]; + args[0].value_uint32 = buffers; + args[1].value_float = color.red; + args[2].value_float = color.green; + args[3].value_float = color.blue; + args[4].value_float = color.alpha; + args[5].value_float = depth; + args[6].value_uint32 = stencil; + cmd_buffer->AddCommand(command_buffer::CLEAR, 7, args); +} + +// Adds a SetViewport command into the buffer. +// Parameters: +// cmd_buffer: the command buffer helper. +// x, y, width, height: the dimensions of the Viewport. +// z_near, z_far: the near and far clip plane distances. +void SetViewportCmd(CommandBufferHelper *cmd_buffer, + unsigned int x, + unsigned int y, + unsigned int width, + unsigned int height, + float z_near, + float z_far) { + CommandBufferEntry args[6]; + args[0].value_uint32 = x; + args[1].value_uint32 = y; + args[2].value_uint32 = width; + args[3].value_uint32 = height; + args[4].value_float = z_near; + args[5].value_float = z_far; + cmd_buffer->AddCommand(command_buffer::SET_VIEWPORT, 6, args); +} + +// Copy a data buffer to args, for IMMEDIATE commands. Returns the number of +// args used. +unsigned int CopyToArgs(CommandBufferEntry *args, + const void *data, + size_t size) { + memcpy(args, data, size); + const unsigned int arg_size = sizeof(args[0]); + return static_cast<unsigned int>((size + arg_size - 1) / arg_size); +} + +// Our effect: pass through position and UV, look up texture. +// This follows the command buffer effect format : +// vertex_program_entry \0 fragment_program_entry \0 effect_code. +const char effect_data[] = + "vs\0" // Vertex program entry point + "ps\0" // Fragment program entry point + "struct a2v {float4 pos: POSITION; float2 uv: TEXCOORD0;};\n" + "struct v2f {float4 pos: POSITION; float2 uv: TEXCOORD0;};\n" + "float4x4 worldViewProj : WorldViewProjection;\n" + "v2f vs(a2v i) {\n" + " v2f o;\n" + " o.pos = mul(i.pos, worldViewProj);\n" + " o.uv = i.uv;\n" + " return o;\n" + "}\n" + "sampler s0;\n" + "float4 ps(v2f i) : COLOR { return tex2D(s0, i.uv); }\n"; + +// Custom vertex, with position and color. +struct CustomVertex { + float x, y, z, w; + float u, v; +}; + +void BigTestClient(nacl::HtpHandle handle) { + IMCSender sender(handle); + BufferSyncProxy proxy(&sender); + + proxy.InitConnection(); + const int kShmSize = 2048; + RPCShmHandle shm = CreateShm(kShmSize); + void *shm_address = MapShm(shm, kShmSize); + unsigned int shm_id = proxy.RegisterSharedMemory(shm, kShmSize); + + { + CommandBufferHelper cmd_buffer(&proxy); + cmd_buffer.Init(500); + + // Clear the buffers. + RGBA color = {0.2f, 0.2f, 0.2f, 1.f}; + ClearCmd(&cmd_buffer, GAPIInterface::COLOR | GAPIInterface::DEPTH, color, + 1.f, 0); + + const ResourceID vertex_buffer_id = 1; + const ResourceID vertex_struct_id = 1; + + // AddCommand copies the args, so it is safe to re-use args across various + // calls. + // 20 is the largest command we use (SET_PARAM_DATA_IMMEDIATE for matrices). + CommandBufferEntry args[20]; + + CustomVertex vertices[4] = { + {-.5f, -.5f, 0.f, 1.f, 0, 0}, + {.5f, -.5f, 0.f, 1.f, 1, 0}, + {-.5f, .5f, 0.f, 1.f, 0, 1}, + {.5f, .5f, 0.f, 1.f, 1, 1}, + }; + args[0].value_uint32 = vertex_buffer_id; + args[1].value_uint32 = sizeof(vertices); // size + args[2].value_uint32 = 0; // flags + cmd_buffer.AddCommand(command_buffer::CREATE_VERTEX_BUFFER, 3, args); + + memcpy(shm_address, vertices, sizeof(vertices)); + args[0].value_uint32 = vertex_buffer_id; + args[1].value_uint32 = 0; // offset in VB + args[2].value_uint32 = sizeof(vertices); // size + args[3].value_uint32 = shm_id; // shm + args[4].value_uint32 = 0; // offset in shm + cmd_buffer.AddCommand(command_buffer::SET_VERTEX_BUFFER_DATA, 5, args); + unsigned int token = cmd_buffer.InsertToken(); + + args[0].value_uint32 = vertex_struct_id; + args[1].value_uint32 = 2; // input count + cmd_buffer.AddCommand(command_buffer::CREATE_VERTEX_STRUCT, 2, args); + + // Set POSITION input stream + args[0].value_uint32 = vertex_struct_id; + args[1].value_uint32 = 0; // input + args[2].value_uint32 = vertex_buffer_id; // buffer + args[3].value_uint32 = 0; // offset + args[4].value_uint32 = + set_vertex_input_cmd::Stride::MakeValue(sizeof(CustomVertex)) | + set_vertex_input_cmd::Type::MakeValue(vertex_struct::FLOAT4) | + set_vertex_input_cmd::Semantic::MakeValue(vertex_struct::POSITION) | + set_vertex_input_cmd::SemanticIndex::MakeValue(0); + cmd_buffer.AddCommand(command_buffer::SET_VERTEX_INPUT, 5, args); + + // Set TEXCOORD0 input stream + args[1].value_uint32 = 1; // input + args[3].value_uint32 = 16; // offset + args[4].value_uint32 = + set_vertex_input_cmd::Stride::MakeValue(sizeof(CustomVertex)) | + set_vertex_input_cmd::Type::MakeValue(vertex_struct::FLOAT2) | + set_vertex_input_cmd::Semantic::MakeValue(vertex_struct::TEX_COORD) | + set_vertex_input_cmd::SemanticIndex::MakeValue(0); + cmd_buffer.AddCommand(command_buffer::SET_VERTEX_INPUT, 5, args); + + // wait for previous transfer to be executed, so that we can re-use the + // transfer shared memory buffer. + cmd_buffer.WaitForToken(token); + memcpy(shm_address, effect_data, sizeof(effect_data)); + const ResourceID effect_id = 1; + args[0].value_uint32 = effect_id; + args[1].value_uint32 = sizeof(effect_data); // size + args[2].value_uint32 = shm_id; // shm + args[3].value_uint32 = 0; // offset in shm + cmd_buffer.AddCommand(command_buffer::CREATE_EFFECT, 4, args); + token = cmd_buffer.InsertToken(); + + // Create a 4x4 2D texture. + const ResourceID texture_id = 1; + args[0].value_uint32 = texture_id; + args[1].value_uint32 = + create_texture_2d_cmd::Width::MakeValue(4) | + create_texture_2d_cmd::Height::MakeValue(4); + args[2].value_uint32 = + create_texture_2d_cmd::Levels::MakeValue(0) | + create_texture_2d_cmd::Format::MakeValue(texture::ARGB8) | + create_texture_2d_cmd::Flags::MakeValue(0); + cmd_buffer.AddCommand(command_buffer::CREATE_TEXTURE_2D, 3, args); + + unsigned int texels[4] = { + 0xff0000ff, + 0xffff00ff, + 0xff00ffff, + 0xffffffff, + }; + // wait for previous transfer to be executed, so that we can re-use the + // transfer shared memory buffer. + cmd_buffer.WaitForToken(token); + memcpy(shm_address, texels, sizeof(texels)); + // Creates a 4x4 texture by uploading 2x2 data in each quadrant. + for (unsigned int x = 0; x < 2; ++x) + for (unsigned int y = 0; y < 2; ++y) { + args[0].value_uint32 = texture_id; + args[1].value_uint32 = + set_texture_data_cmd::X::MakeValue(x*2) | + set_texture_data_cmd::Y::MakeValue(y*2); + args[2].value_uint32 = + set_texture_data_cmd::Width::MakeValue(2) | + set_texture_data_cmd::Height::MakeValue(2); + args[3].value_uint32 = + set_texture_data_cmd::Z::MakeValue(0) | + set_texture_data_cmd::Depth::MakeValue(1); + args[4].value_uint32 = set_texture_data_cmd::Level::MakeValue(0); + args[5].value_uint32 = sizeof(texels[0]) * 2; // row_pitch + args[6].value_uint32 = 0; // slice_pitch + args[7].value_uint32 = sizeof(texels); // size + args[8].value_uint32 = shm_id; + args[9].value_uint32 = 0; + cmd_buffer.AddCommand(command_buffer::SET_TEXTURE_DATA, 10, args); + } + token = cmd_buffer.InsertToken(); + + const ResourceID sampler_id = 1; + args[0].value_uint32 = sampler_id; + cmd_buffer.AddCommand(command_buffer::CREATE_SAMPLER, 1, args); + + args[0].value_uint32 = sampler_id; + args[1].value_uint32 = texture_id; + cmd_buffer.AddCommand(command_buffer::SET_SAMPLER_TEXTURE, 2, args); + + args[0].value_uint32 = sampler_id; + args[1].value_uint32 = + set_sampler_states::AddressingU::MakeValue(sampler::CLAMP_TO_EDGE) | + set_sampler_states::AddressingV::MakeValue(sampler::CLAMP_TO_EDGE) | + set_sampler_states::AddressingW::MakeValue(sampler::CLAMP_TO_EDGE) | + set_sampler_states::MagFilter::MakeValue(sampler::POINT) | + set_sampler_states::MinFilter::MakeValue(sampler::POINT) | + set_sampler_states::MipFilter::MakeValue(sampler::NONE) | + set_sampler_states::MaxAnisotropy::MakeValue(1); + cmd_buffer.AddCommand(command_buffer::SET_SAMPLER_STATES, 2, args); + + // Create a parameter for the sampler. + const ResourceID sampler_param_id = 1; + { + const char param_name[] = "s0"; + args[0].value_uint32 = sampler_param_id; + args[1].value_uint32 = effect_id; + args[2].value_uint32 = sizeof(param_name); + unsigned int arg_count = CopyToArgs(args + 3, param_name, + sizeof(param_name)); + cmd_buffer.AddCommand(command_buffer::CREATE_PARAM_BY_NAME_IMMEDIATE, + 3 + arg_count, args); + } + + const ResourceID matrix_param_id = 2; + { + const char param_name[] = "worldViewProj"; + args[0].value_uint32 = matrix_param_id; + args[1].value_uint32 = effect_id; + args[2].value_uint32 = sizeof(param_name); + unsigned int arg_count = CopyToArgs(args + 3, param_name, + sizeof(param_name)); + cmd_buffer.AddCommand(command_buffer::CREATE_PARAM_BY_NAME_IMMEDIATE, + 3 + arg_count, args); + } + + float t = 0.f; + while (true) { + t = fmodf(t + .01f, 1.f); + math::Matrix4 m = + math::Matrix4::translation(math::Vector3(0.f, 0.f, .5f)); + m *= math::Matrix4::rotationY(t * 2 * 3.1415926f); + cmd_buffer.AddCommand(command_buffer::BEGIN_FRAME, 0 , NULL); + // Clear the background with an animated color (black to red). + ClearCmd(&cmd_buffer, GAPIInterface::COLOR | GAPIInterface::DEPTH, color, + 1.f, 0); + + args[0].value_uint32 = vertex_struct_id; + cmd_buffer.AddCommand(command_buffer::SET_VERTEX_STRUCT, 1, args); + + args[0].value_uint32 = effect_id; + cmd_buffer.AddCommand(command_buffer::SET_EFFECT, 1, args); + + args[0].value_uint32 = sampler_param_id; + args[1].value_uint32 = sizeof(Uint32); // NOLINT + args[2].value_uint32 = sampler_id; + cmd_buffer.AddCommand(command_buffer::SET_PARAM_DATA_IMMEDIATE, 3, args); + + args[0].value_uint32 = matrix_param_id; + args[1].value_uint32 = sizeof(m); + unsigned int arg_count = CopyToArgs(args + 2, &m, sizeof(m)); + cmd_buffer.AddCommand(command_buffer::SET_PARAM_DATA_IMMEDIATE, + 2 + arg_count, args); + + args[0].value_uint32 = GAPIInterface::TRIANGLE_STRIPS; + args[1].value_uint32 = 0; // first + args[2].value_uint32 = 2; // primitive count + cmd_buffer.AddCommand(command_buffer::DRAW, 3, args); + + cmd_buffer.AddCommand(command_buffer::END_FRAME, 0 , NULL); + cmd_buffer.Flush(); + } + + cmd_buffer.Finish(); + } + + proxy.CloseConnection(); + proxy.UnregisterSharedMemory(shm_id); + DestroyShm(shm); + + sender.SendCall(POISONED_MESSAGE_ID, NULL, 0, NULL, 0); +} + +} // namespace command_buffer +} // namespace o3d + +nacl::HtpHandle InitConnection(int argc, char **argv) { + nacl::Handle handle = nacl::kInvalidHandle; +#ifndef __native_client__ + NaClNrdAllModulesInit(); + + static nacl::SocketAddress g_address = { "command-buffer" }; + static nacl::SocketAddress g_local_address = { "cb-client" }; + + nacl::Handle sockets[2]; + nacl::SocketPair(sockets); + + nacl::MessageHeader msg; + msg.iov = NULL; + msg.iov_length = 0; + msg.handles = &sockets[1]; + msg.handle_count = 1; + nacl::Handle local_socket = nacl::BoundSocket(&g_local_address); + nacl::SendDatagramTo(local_socket, &msg, 0, &g_address); + nacl::Close(local_socket); + nacl::Close(sockets[1]); + handle = sockets[0]; +#else + if (argc < 3 || strcmp(argv[1], "-fd") != 0) { + fprintf(stderr, "Usage: %s -fd file_descriptor\n", argv[0]); + return nacl::kInvalidHtpHandle; + } + int fd = atoi(argv[2]); + handle = imc_connect(fd); + if (handle < 0) { + fprintf(stderr, "Could not connect to file descriptor %d.\n" + "Did you use the -a and -X options to sel_ldr ?\n", fd); + return nacl::kInvalidHtpHandle; + } +#endif + return nacl::CreateImcDesc(handle); +} + +void CloseConnection(nacl::HtpHandle handle) { + nacl::Close(handle); +#ifndef __native_client__ + NaClNrdAllModulesFini(); +#endif +} + +int main(int argc, char **argv) { + nacl::HtpHandle htp_handle = InitConnection(argc, argv); + if (htp_handle == nacl::kInvalidHtpHandle) { + return 1; + } + + o3d::command_buffer::BigTestClient(htp_handle); + CloseConnection(htp_handle); + return 0; +} diff --git a/o3d/command_buffer/client/cross/buffer_sync_proxy.cc b/o3d/command_buffer/client/cross/buffer_sync_proxy.cc new file mode 100644 index 0000000..8439085 --- /dev/null +++ b/o3d/command_buffer/client/cross/buffer_sync_proxy.cc @@ -0,0 +1,128 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +// This file has the implementation of the Command Buffer Synchronous API RPC +// glue, client-side (proxy). + +#include "command_buffer/client/cross/buffer_sync_proxy.h" +#include "command_buffer/service/cross/buffer_rpc.h" + +namespace o3d { +namespace command_buffer { + +// Implements the proxy InitConnection, forwarding the call with its argument to +// the RPC server. +void BufferSyncProxy::InitConnection() { + server_->SendCall(BufferRPCImpl::INIT_CONNECTION, NULL, 0, NULL, 0); +} + +// Implements the proxy CloseConnection, forwarding the call to the RPC server. +void BufferSyncProxy::CloseConnection() { + server_->SendCall(BufferRPCImpl::CLOSE_CONNECTION, NULL, 0, NULL, 0); +} + +unsigned int BufferSyncProxy::RegisterSharedMemory(RPCShmHandle buffer, + size_t size) { + RPCHandle handles[1] = {buffer}; + return server_->SendCall(BufferRPCImpl::REGISTER_SHARED_MEMORY, &size, + sizeof(size), handles, 1); +} + +void BufferSyncProxy::UnregisterSharedMemory(unsigned int shm_id) { + server_->SendCall(BufferRPCImpl::UNREGISTER_SHARED_MEMORY, &shm_id, + sizeof(shm_id), NULL, 0); +} + +// Implements the proxy SetCommandBuffer, forwarding the call with its +// arguments to the RPC server. +void BufferSyncProxy::SetCommandBuffer(unsigned int shm_id, + ptrdiff_t offset, + size_t size, + CommandBufferOffset start_get) { + BufferRPCImpl::SetCommandBufferStruct params; + params.shm_id = shm_id; + params.offset = offset; + params.size = size; + params.start_get = start_get; + server_->SendCall(BufferRPCImpl::SET_COMMAND_BUFFER, ¶ms, sizeof(params), + NULL, 0); +} + +// Implements the proxy Put, forwarding the call with its argument to the RPC +// server. +void BufferSyncProxy::Put(CommandBufferOffset offset) { + server_->SendCall(BufferRPCImpl::PUT, &offset, sizeof(offset), NULL, 0); +} + +// Implements the proxy Get, forwarding the call to the RPC server. +CommandBufferOffset BufferSyncProxy::Get() { + return server_->SendCall(BufferRPCImpl::GET, NULL, 0, NULL, 0); +} + +// Implements the proxy GetToken, forwarding the call to the RPC server. +unsigned int BufferSyncProxy::GetToken() { + return server_->SendCall(BufferRPCImpl::GET_TOKEN, NULL, 0, NULL, 0); +} + +// Implements the proxy WaitGetChanges, forwarding the call with its argument +// to the RPC server. +CommandBufferOffset BufferSyncProxy::WaitGetChanges( + CommandBufferOffset current_value) { + return server_->SendCall(BufferRPCImpl::WAIT_GET_CHANGES, ¤t_value, + sizeof(current_value), NULL, 0); +} + +// Implements the proxy SignalGetChanges, forwarding the call with its +// arguments to the RPC server. +void BufferSyncProxy::SignalGetChanges(CommandBufferOffset current_value, + int rpc_message_id) { + BufferRPCImpl::SignalGetChangesStruct params; + params.current_value = current_value; + params.rpc_message_id = rpc_message_id; + server_->SendCall(BufferRPCImpl::SIGNAL_GET_CHANGES, ¶ms, sizeof(params), + NULL, 0); +} + +// Implements the proxy GetStatus, forwarding the call to the RPC server. +BufferSyncInterface::ParserStatus BufferSyncProxy::GetStatus() { + return static_cast<BufferSyncInterface::ParserStatus>( + server_->SendCall(BufferRPCImpl::GET_STATUS, NULL, 0, NULL, 0)); +} + +// Implements the proxy GetParseError, forwarding the call to the RPC server. +BufferSyncInterface::ParseError BufferSyncProxy::GetParseError() { + return static_cast<ParseError>( + server_->SendCall(BufferRPCImpl::GET_PARSE_ERROR, NULL, 0, NULL, 0)); +} + +} // namespace command_buffer +} // namespace o3d diff --git a/o3d/command_buffer/client/cross/buffer_sync_proxy.h b/o3d/command_buffer/client/cross/buffer_sync_proxy.h new file mode 100644 index 0000000..341b935 --- /dev/null +++ b/o3d/command_buffer/client/cross/buffer_sync_proxy.h @@ -0,0 +1,102 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +// This file defines the RPC glue for the Command Buffer Synchronous API, +// client side: a proxy implementation of BufferSyncInterface, forwarding calls +// to a RPC send interface. + +#ifndef O3D_COMMAND_BUFFER_CLIENT_CROSS_BUFFER_SYNC_PROXY_H_ +#define O3D_COMMAND_BUFFER_CLIENT_CROSS_BUFFER_SYNC_PROXY_H_ + +#include "command_buffer/common/cross/rpc.h" +#include "command_buffer/common/cross/buffer_sync_api.h" + +namespace o3d { +namespace command_buffer { + +// Class implementing the Command Buffer Synchronous API, forwarding all the +// calls to a RPC server, according to the (trivial) protocol defined in +// BufferRPCImpl. +class BufferSyncProxy : public BufferSyncInterface { + public: + explicit BufferSyncProxy(RPCSendInterface *server) : server_(server) {} + virtual ~BufferSyncProxy() {} + + // Implements the InitConnection call, forwarding it to the RPC server. + virtual void InitConnection(); + + // Implements the CloseConnection call, forwarding it to the RPC server. + virtual void CloseConnection(); + + // Implements the RegisterSharedMemory call, forwarding it to the RPC server. + virtual unsigned int RegisterSharedMemory(RPCShmHandle buffer, size_t size); + + // Implements the UnregisterSharedMemory call, forwarding it to the RPC + // server. + virtual void UnregisterSharedMemory(unsigned int shm_id); + + // Implements the SetCommandBuffer call, forwarding it to the RPC server. + virtual void SetCommandBuffer(unsigned int shm_id, + ptrdiff_t offset, + size_t size, + CommandBufferOffset start_get); + + // Implements the Put call, forwarding it to the RPC server. + virtual void Put(CommandBufferOffset offset); + + // Implements the Get call, forwarding it to the RPC server. + virtual CommandBufferOffset Get(); + + // Implements the GetToken call, forwarding it to the RPC server. + virtual unsigned int GetToken(); + + // Implements the WaitGetChanges call, forwarding it to the RPC server. + virtual CommandBufferOffset WaitGetChanges( + CommandBufferOffset current_value); + + // Implements the SignalGetChanges call, forwarding it to the RPC server. + virtual void SignalGetChanges(CommandBufferOffset current_value, + int rpc_message_id); + + // Implements the GetStatus call, forwarding it to the RPC server. + virtual ParserStatus GetStatus(); + + // Implements the GetParseError call, forwarding it to the RPC server. + virtual ParseError GetParseError(); + private: + RPCSendInterface *server_; +}; + +} // namespace command_buffer +} // namespace o3d + +#endif // O3D_COMMAND_BUFFER_CLIENT_CROSS_BUFFER_SYNC_PROXY_H_ diff --git a/o3d/command_buffer/client/cross/buffer_sync_proxy_test.cc b/o3d/command_buffer/client/cross/buffer_sync_proxy_test.cc new file mode 100644 index 0000000..790a693 --- /dev/null +++ b/o3d/command_buffer/client/cross/buffer_sync_proxy_test.cc @@ -0,0 +1,260 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +// Tests for the Command Buffer RPC glue, client side (proxy). + +#include "base/scoped_ptr.h" +#include "tests/common/win/testing_common.h" +#include "command_buffer/client/cross/buffer_sync_proxy.h" +#include "command_buffer/common/cross/mocks.h" +#include "command_buffer/service/cross/buffer_rpc.h" + +namespace o3d { +namespace command_buffer { + +using testing::Return; + +// Test fixture for BufferSyncProxy test - Creates a BufferSyncProxy, using a +// mock RPCSendInterface. +class BufferSyncProxyTest : public testing::Test { + protected: + virtual void SetUp() { + server_mock_.reset(new RPCSendInterfaceMock); + proxy_.reset(new BufferSyncProxy(server_mock_.get())); + } + virtual void TearDown() {} + + RPCSendInterfaceMock *server_mock() { return server_mock_.get(); } + BufferSyncProxy *proxy() { return proxy_.get(); } + private: + scoped_ptr<RPCSendInterfaceMock> server_mock_; + scoped_ptr<BufferSyncProxy> proxy_; +}; + +// Tests the implementation of InitConnection, checking that it sends the +// correct message. +TEST_F(BufferSyncProxyTest, TestInitConnection) { + RPCSendInterfaceMock::SendCallExpect expect; + expect._return = 0; + expect.message_id = BufferRPCImpl::INIT_CONNECTION; + expect.data = NULL; + expect.size = 0; + expect.handles = NULL; + expect.handle_count = 0; + server_mock()->AddSendCallExpect(expect); + + proxy()->InitConnection(); +} + +// Tests the implementation of CloseConnection, checking that it sends the +// correct message. +TEST_F(BufferSyncProxyTest, TestCloseConnection) { + RPCSendInterfaceMock::SendCallExpect expect; + expect._return = 0; + expect.message_id = BufferRPCImpl::CLOSE_CONNECTION; + expect.data = NULL; + expect.size = 0; + expect.handles = NULL; + expect.handle_count = 0; + server_mock()->AddSendCallExpect(expect); + + proxy()->CloseConnection(); +} + +// Tests the implementation of RegisterSharedMemory, checking that it sends the +// correct message and returns the correct value. +TEST_F(BufferSyncProxyTest, TestRegisterSharedMemory) { + RPCShmHandle shm = reinterpret_cast<RPCShmHandle>(456); + RPCHandle handles[1] = {shm}; + size_t size = 789; + RPCSendInterfaceMock::SendCallExpect expect; + expect._return = 123; + expect.message_id = BufferRPCImpl::REGISTER_SHARED_MEMORY; + expect.data = &size; + expect.size = sizeof(size); + expect.handles = handles; + expect.handle_count = 1; + server_mock()->AddSendCallExpect(expect); + + EXPECT_EQ(123, proxy()->RegisterSharedMemory(shm, size)); +} + +// Tests the implementation of UnregisterSharedMemory, checking that it sends +// the correct message. +TEST_F(BufferSyncProxyTest, TestUnregisterSharedMemory) { + unsigned int shm_id = 456; + RPCSendInterfaceMock::SendCallExpect expect; + expect._return = 0; + expect.message_id = BufferRPCImpl::UNREGISTER_SHARED_MEMORY; + expect.data = &shm_id; + expect.size = sizeof(shm_id); + expect.handles = NULL; + expect.handle_count = 0; + server_mock()->AddSendCallExpect(expect); + + proxy()->UnregisterSharedMemory(456); +} + +// Tests the implementation of SetCommandBuffer, checking that it sends the +// correct message. +TEST_F(BufferSyncProxyTest, TestSetCommandBuffer) { + BufferRPCImpl::SetCommandBufferStruct params; + params.shm_id = 53; + params.offset = 1234; + params.size = 5678; + params.start_get = 42; + + RPCSendInterfaceMock::SendCallExpect expect; + expect._return = 0; + expect.message_id = BufferRPCImpl::SET_COMMAND_BUFFER; + expect.data = ¶ms; + expect.size = sizeof(params); + expect.handles = NULL; + expect.handle_count = 0; + server_mock()->AddSendCallExpect(expect); + + proxy()->SetCommandBuffer(53, 1234, 5678, 42); +} + +// Tests the implementation of Put, checking that it sends the correct message. +TEST_F(BufferSyncProxyTest, TestPut) { + RPCSendInterfaceMock::SendCallExpect expect; + expect._return = 0; + expect.message_id = BufferRPCImpl::PUT; + CommandBufferOffset value = 67; + expect.data = &value; + expect.size = sizeof(value); + expect.handles = NULL; + expect.handle_count = 0; + server_mock()->AddSendCallExpect(expect); + + proxy()->Put(67); +} + +// Tests the implementation of Get, checking that it sends the correct message +// and returns the correct value. +TEST_F(BufferSyncProxyTest, TestGet) { + RPCSendInterfaceMock::SendCallExpect expect; + expect._return = 72; + expect.message_id = BufferRPCImpl::GET; + expect.data = NULL; + expect.size = 0; + expect.handles = NULL; + expect.handle_count = 0; + server_mock()->AddSendCallExpect(expect); + + EXPECT_EQ(72, proxy()->Get()); +} + +// Tests the implementation of GetToken, checking that it sends the correct +// message and returns the correct value. +TEST_F(BufferSyncProxyTest, TestGetToken) { + RPCSendInterfaceMock::SendCallExpect expect; + expect._return = 38; + expect.message_id = BufferRPCImpl::GET_TOKEN; + expect.data = NULL; + expect.size = 0; + expect.handles = NULL; + expect.handle_count = 0; + server_mock()->AddSendCallExpect(expect); + + EXPECT_EQ(38, proxy()->GetToken()); +} + +// Tests the implementation of WaitGetChanges, checking that it sends the +// correct message and returns the correct value. +TEST_F(BufferSyncProxyTest, TestWaitGetChanges) { + RPCSendInterfaceMock::SendCallExpect expect; + expect._return = 53; + expect.message_id = BufferRPCImpl::WAIT_GET_CHANGES; + CommandBufferOffset value = 101; + expect.data = &value; + expect.size = sizeof(value); + expect.handles = NULL; + expect.handle_count = 0; + server_mock()->AddSendCallExpect(expect); + + EXPECT_EQ(53, proxy()->WaitGetChanges(101)); +} + +// Tests the implementation of SignalGetChanges, checking that it sends the +// correct message. +TEST_F(BufferSyncProxyTest, TestSignalGetChanges) { + BufferRPCImpl::SignalGetChangesStruct params; + params.current_value = 3141; + params.rpc_message_id = 5926; + + RPCSendInterfaceMock::SendCallExpect expect; + expect._return = 0; + expect.message_id = BufferRPCImpl::SIGNAL_GET_CHANGES; + expect.data = ¶ms; + expect.size = sizeof(params); + expect.handles = NULL; + expect.handle_count = 0; + server_mock()->AddSendCallExpect(expect); + + proxy()->SignalGetChanges(3141, 5926); +} + +// Tests the implementation of GetStatus, checking that it sends the correct +// message and returns the correct value. +TEST_F(BufferSyncProxyTest, TestGetStatus) { + RPCSendInterfaceMock::SendCallExpect expect; + expect._return = BufferSyncInterface::PARSING; + expect.message_id = BufferRPCImpl::GET_STATUS; + expect.data = NULL; + expect.size = 0; + expect.handles = NULL; + expect.handle_count = 0; + server_mock()->AddSendCallExpect(expect); + + EXPECT_EQ(BufferSyncInterface::PARSING, proxy()->GetStatus()); +} + +// Tests the implementation of GetParseError, checking that it sends the correct +// message and returns the correct value. +TEST_F(BufferSyncProxyTest, TestGetParseError) { + RPCSendInterfaceMock::SendCallExpect expect; + expect._return = BufferSyncInterface::PARSE_UNKNOWN_COMMAND; + expect.message_id = BufferRPCImpl::GET_PARSE_ERROR; + expect.data = NULL; + expect.size = 0; + expect.handles = NULL; + expect.handle_count = 0; + server_mock()->AddSendCallExpect(expect); + + EXPECT_EQ(BufferSyncInterface::PARSE_UNKNOWN_COMMAND, + proxy()->GetParseError()); +} + +} // namespace command_buffer +} // namespace o3d diff --git a/o3d/command_buffer/client/cross/cmd_buffer_helper.cc b/o3d/command_buffer/client/cross/cmd_buffer_helper.cc new file mode 100644 index 0000000..481f0c7 --- /dev/null +++ b/o3d/command_buffer/client/cross/cmd_buffer_helper.cc @@ -0,0 +1,185 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +// This file contains the implementation of the command buffer helper class. + +#include "command_buffer/client/cross/cmd_buffer_helper.h" + +namespace o3d { +namespace command_buffer { + +CommandBufferHelper::CommandBufferHelper(BufferSyncInterface *interface) + : interface_(interface), + entries_(NULL), + entry_count_(0), + token_(0) { + // The interface should be connected already. + DCHECK_NE(BufferSyncInterface::NOT_CONNECTED, interface_->GetStatus()); +} + +bool CommandBufferHelper::Init(unsigned int entry_count) { + if (entry_count == 0) + return false; + size_t size = entry_count * sizeof(CommandBufferEntry); // NOLINT + shm_handle_ = CreateShm(size); + if (shm_handle_ == kRPCInvalidHandle) + return false; + void *address = MapShm(shm_handle_, size); + if (!address) { + DestroyShm(shm_handle_); + shm_handle_ = kRPCInvalidHandle; + return false; + } + entries_ = static_cast<CommandBufferEntry *>(address); + entry_count_ = entry_count; + shm_id_ = interface_->RegisterSharedMemory(shm_handle_, size); + interface_->SetCommandBuffer(shm_id_, 0, size, 0); + get_ = interface_->Get(); + put_ = get_; + last_token_read_ = interface_->GetToken(); + return true; +} + +CommandBufferHelper::~CommandBufferHelper() { + if (entries_) { + interface_->UnregisterSharedMemory(shm_id_); + UnmapShm(entries_, entry_count_ * sizeof(CommandBufferEntry)); // NOLINT + DestroyShm(shm_handle_); + } +} + +// Calls Flush() and then waits until the buffer is empty. Break early if the +// error is set. +void CommandBufferHelper::Finish() { + Flush(); + while (put_ != get_) { + WaitForGetChange(); + } +} + +// Inserts a new token into the command stream. It uses an increasing value +// scheme so that we don't lose tokens (a token has passed if the current token +// value is higher than that token). Calls Finish() if the token value wraps, +// which will be rare. +unsigned int CommandBufferHelper::InsertToken() { + ++token_; + CommandBufferEntry args; + args.value_uint32 = token_; + AddCommand(SET_TOKEN, 1, &args); + if (token_ == 0) { + // we wrapped + Finish(); + last_token_read_ = interface_->GetToken(); + DCHECK_EQ(token_, last_token_read_); + } + return token_; +} + +// Waits until the current token value is greater or equal to the value passed +// in argument. +void CommandBufferHelper::WaitForToken(unsigned int token) { + if (last_token_read_ >= token) return; // fast path. + if (token > token_) return; // we wrapped + Flush(); + last_token_read_ = interface_->GetToken(); + while (last_token_read_ < token) { + if (get_ == put_) { + LOG(FATAL) << "Empty command buffer while waiting on a token."; + return; + } + WaitForGetChange(); + last_token_read_ = interface_->GetToken(); + } +} + +// Waits for get to change. In case get doesn't change or becomes invalid, +// check for an error. +void CommandBufferHelper::WaitForGetChange() { + CommandBufferOffset new_get = interface_->WaitGetChanges(get_); + if (new_get == get_ || new_get == -1) { + // If get_ didn't change or is invalid (-1), it means an error may have + // occured. Check that. + BufferSyncInterface::ParserStatus status = interface_->GetStatus(); + if (status != BufferSyncInterface::PARSING) { + switch (status) { + case BufferSyncInterface::NOT_CONNECTED: + LOG(FATAL) << "Service disconnected."; + return; + case BufferSyncInterface::NO_BUFFER: + LOG(FATAL) << "Service doesn't have a buffer set."; + return; + case BufferSyncInterface::PARSE_ERROR: { + BufferSyncInterface::ParseError error = interface_->GetParseError(); + LOG(WARNING) << "Parse error: " << error; + return; + } + case BufferSyncInterface::PARSING: + break; + } + } + } + get_ = new_get; +} + +// Waits for available entries, basically waiting until get >= put + count + 1. +// It actually waits for contiguous entries, so it may need to wrap the buffer +// around, adding noops. Thus this function may change the value of put_. +// The function will return early if an error occurs, in which case the +// available space may not be available. +void CommandBufferHelper::WaitForAvailableEntries(unsigned int count) { + CHECK(count < entry_count_); + if (put_ + count > entry_count_) { + // There's not enough room between the current put and the end of the + // buffer, so we need to wrap. We will add noops all the way to the end, + // but we need to make sure get wraps first, actually that get is 1 or + // more (since put will wrap to 0 after we add the noops). + DCHECK_LE(1, put_); + Flush(); + while (get_ > put_ || get_ == 0) WaitForGetChange(); + // Add the noops. By convention, a noop is a command 0 with no args. + CommandHeader header; + header.size = 1; + header.command = 0; + while (put_ < entry_count_) { + entries_[put_++].value_header = header; + } + put_ = 0; + } + // If we have enough room, return immediatly. + if (count <= AvailableEntries()) return; + // Otherwise flush, and wait until we do have enough room. + Flush(); + while (AvailableEntries() < count) WaitForGetChange(); +} + +} // namespace command_buffer +} // namespace o3d diff --git a/o3d/command_buffer/client/cross/cmd_buffer_helper.h b/o3d/command_buffer/client/cross/cmd_buffer_helper.h new file mode 100644 index 0000000..775835b --- /dev/null +++ b/o3d/command_buffer/client/cross/cmd_buffer_helper.h @@ -0,0 +1,158 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +// This file contains the command buffer helper class. + +#ifndef O3D_COMMAND_BUFFER_CLIENT_CROSS_CMD_BUFFER_HELPER_H_ +#define O3D_COMMAND_BUFFER_CLIENT_CROSS_CMD_BUFFER_HELPER_H_ + +#include "command_buffer/common/cross/logging.h" +#include "command_buffer/common/cross/buffer_sync_api.h" +#include "command_buffer/common/cross/cmd_buffer_format.h" + +namespace o3d { +namespace command_buffer { + +// Command buffer helper class. This class simplifies ring buffer management: +// it will allocate the buffer, give it to the buffer interface, and let the +// user add commands to it, while taking care of the synchronization (put and +// get). It also provides a way to ensure commands have been executed, through +// the token mechanism: +// +// helper.AddCommand(...); +// helper.AddCommand(...); +// unsigned int token = helper.InsertToken(); +// helper.AddCommand(...); +// helper.AddCommand(...); +// [...] +// +// helper.WaitForToken(token); // this doesn't return until the first two +// // commands have been executed. +class CommandBufferHelper { + public: + // Constructs a CommandBufferHelper object. The helper needs to be + // initialized by calling Init() before use. + // Parameters: + // interface: the buffer interface the helper sends commands to. + explicit CommandBufferHelper(BufferSyncInterface *interface); + ~CommandBufferHelper(); + + // Initializes the command buffer by allocating shared memory. + // Parameters: + // entry_count: the number of entries in the buffer. Note that commands + // sent through the buffer must use at most entry_count-2 arguments + // (entry_count-1 size). + // Returns: + // true if successful. + bool Init(unsigned int entry_count); + + // Flushes the commands, setting the put pointer to let the buffer interface + // know that new commands have been added. + void Flush() { + interface_->Put(put_); + } + + // Waits until all the commands have been executed. + void Finish(); + + // Waits until a given number of available entries are available. + // Parameters: + // count: number of entries needed. This value must be at most + // the size of the buffer minus one. + void WaitForAvailableEntries(unsigned int count); + + // Adds a command to the command buffer. This may wait until sufficient space + // is available. + // Parameters: + // command: the command index. + // arg_count: the number of arguments for the command. + // args: the arguments for the command (these are copied before the + // function returns). + void AddCommand(unsigned int command, + unsigned int arg_count, + CommandBufferEntry *args) { + CommandHeader header; + header.size = arg_count + 1; + header.command = command; + WaitForAvailableEntries(header.size); + entries_[put_++].value_header = header; + for (unsigned int i = 0; i < arg_count; ++i) { + entries_[put_++] = args[i]; + } + DCHECK_LE(put_, entry_count_); + if (put_ == entry_count_) put_ = 0; + } + + // Inserts a new token into the command buffer. This token either has a value + // different from previously inserted tokens, or ensures that previously + // inserted tokens with that value have already passed through the command + // stream. + // Returns: + // the value of the new token. + unsigned int InsertToken(); + + // Waits until the token of a particular value has passed through the command + // stream (i.e. commands inserted before that token have been executed). + // NOTE: This will call Flush if it needs to block. + // Parameters: + // the value of the token to wait for. + void WaitForToken(unsigned int token); + + // Returns the buffer interface used to send synchronous commands. + BufferSyncInterface *interface() { return interface_; } + + private: + // Waits until get changes, updating the value of get_. + void WaitForGetChange(); + + // Returns the number of available entries (they may not be contiguous). + unsigned int AvailableEntries() { + return (get_ - put_ - 1 + entry_count_) % entry_count_; + } + + BufferSyncInterface *interface_; + CommandBufferEntry *entries_; + unsigned int entry_count_; + unsigned int token_; + unsigned int last_token_read_; + RPCShmHandle shm_handle_; + unsigned int shm_id_; + CommandBufferOffset get_; + CommandBufferOffset put_; + + friend class CommandBufferHelperTest; +}; + +} // namespace command_buffer +} // namespace o3d + +#endif // O3D_COMMAND_BUFFER_CLIENT_CROSS_CMD_BUFFER_HELPER_H_ diff --git a/o3d/command_buffer/client/cross/cmd_buffer_helper_test.cc b/o3d/command_buffer/client/cross/cmd_buffer_helper_test.cc new file mode 100644 index 0000000..e25bcd9 --- /dev/null +++ b/o3d/command_buffer/client/cross/cmd_buffer_helper_test.cc @@ -0,0 +1,276 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +// Tests for the Command Buffer Helper. + +#include "tests/common/win/testing_common.h" +#include "command_buffer/client/cross/cmd_buffer_helper.h" +#include "command_buffer/service/cross/cmd_buffer_engine.h" +#include "command_buffer/service/cross/mocks.h" + +namespace o3d { +namespace command_buffer { + +using testing::Return; +using testing::Mock; +using testing::Truly; +using testing::Sequence; +using testing::DoAll; +using testing::Invoke; +using testing::_; + +// Test fixture for CommandBufferHelper test - Creates a CommandBufferHelper, +// using a CommandBufferEngine with a mock AsyncAPIInterface for its interface +// (calling it directly, not through the RPC mechanism). +class CommandBufferHelperTest : public testing::Test { + protected: + virtual void SetUp() { + api_mock_.reset(new AsyncAPIMock); + // ignore noops in the mock - we don't want to inspect the internals of the + // helper. + EXPECT_CALL(*api_mock_, DoCommand(0, 0, _)) + .WillRepeatedly(Return(BufferSyncInterface::PARSE_NO_ERROR)); + engine_.reset(new CommandBufferEngine(api_mock_.get())); + api_mock_->set_engine(engine_.get()); + + engine_->InitConnection(); + helper_.reset(new CommandBufferHelper(engine_.get())); + helper_->Init(10); + } + + virtual void TearDown() { + helper_.release(); + engine_->CloseConnection(); + } + + // Adds a command to the buffer through the helper, while adding it as an + // expected call on the API mock. + void AddCommandWithExpect(BufferSyncInterface::ParseError _return, + unsigned int command, + unsigned int arg_count, + CommandBufferEntry *args) { + helper_->AddCommand(command, arg_count, args); + EXPECT_CALL(*api_mock(), DoCommand(command, arg_count, + Truly(AsyncAPIMock::IsArgs(arg_count, args)))) + .InSequence(sequence_) + .WillOnce(Return(_return)); + } + + // Checks that the buffer from put to put+size is free in the parser. + void CheckFreeSpace(CommandBufferOffset put, unsigned int size) { + CommandBufferOffset parser_put = engine()->parser()->put(); + CommandBufferOffset parser_get = engine()->parser()->get(); + CommandBufferOffset limit = put + size; + if (parser_get > parser_put) { + // "busy" buffer wraps, so "free" buffer is between put (inclusive) and + // get (exclusive). + EXPECT_LE(parser_put, put); + EXPECT_GT(parser_get, limit); + } else { + // "busy" buffer does not wrap, so the "free" buffer is the top side (from + // put to the limit) and the bottom side (from 0 to get). + if (put >= parser_put) { + // we're on the top side, check we are below the limit. + EXPECT_GE(10, limit); + } else { + // we're on the bottom side, check we are below get. + EXPECT_GT(parser_get, limit); + } + } + } + + CommandBufferHelper *helper() { return helper_.get(); } + CommandBufferEngine *engine() { return engine_.get(); } + AsyncAPIMock *api_mock() { return api_mock_.get(); } + CommandBufferOffset get_helper_put() { return helper_->put_; } + private: + scoped_ptr<AsyncAPIMock> api_mock_; + scoped_ptr<CommandBufferEngine> engine_; + scoped_ptr<CommandBufferHelper> helper_; + Sequence sequence_; +}; + +// Checks that commands in the buffer are properly executed, and that the +// status/error stay valid. +TEST_F(CommandBufferHelperTest, TestCommandProcessing) { + // Check initial state of the engine - it should have been configured by the + // helper. + EXPECT_TRUE(engine()->rpc_impl() != NULL); + EXPECT_TRUE(engine()->parser() != NULL); + EXPECT_EQ(BufferSyncInterface::PARSING, engine()->GetStatus()); + EXPECT_EQ(BufferSyncInterface::PARSE_NO_ERROR, engine()->GetParseError()); + EXPECT_EQ(0, engine()->Get()); + + // Add 3 commands through the helper + AddCommandWithExpect(BufferSyncInterface::PARSE_NO_ERROR, 1, 0, NULL); + + CommandBufferEntry args1[2]; + args1[0].value_uint32 = 3; + args1[1].value_float = 4.f; + AddCommandWithExpect(BufferSyncInterface::PARSE_NO_ERROR, 2, 2, args1); + + CommandBufferEntry args2[2]; + args2[0].value_uint32 = 5; + args2[1].value_float = 6.f; + AddCommandWithExpect(BufferSyncInterface::PARSE_NO_ERROR, 3, 2, args2); + + helper()->Flush(); + // Check that the engine has work to do now. + EXPECT_FALSE(engine()->parser()->IsEmpty()); + + // Wait until it's done. + helper()->Finish(); + // Check that the engine has no more work to do. + EXPECT_TRUE(engine()->parser()->IsEmpty()); + + // Check that the commands did happen. + Mock::VerifyAndClearExpectations(api_mock()); + + // Check the error status. + ASSERT_EQ(BufferSyncInterface::PARSING, engine()->GetStatus()); + EXPECT_EQ(BufferSyncInterface::PARSE_NO_ERROR, engine()->GetParseError()); +} + +// Checks that commands in the buffer are properly executed when wrapping the +// buffer, and that the status/error stay valid. +TEST_F(CommandBufferHelperTest, TestCommandWrapping) { + // Add 5 commands of size 3 through the helper to make sure we do wrap. + CommandBufferEntry args1[2]; + args1[0].value_uint32 = 3; + args1[1].value_float = 4.f; + + for (unsigned int i = 0; i < 5; ++i) { + AddCommandWithExpect(BufferSyncInterface::PARSE_NO_ERROR, i+1, 2, args1); + } + + helper()->Finish(); + // Check that the commands did happen. + Mock::VerifyAndClearExpectations(api_mock()); + + // Check the error status. + ASSERT_EQ(BufferSyncInterface::PARSING, engine()->GetStatus()); + EXPECT_EQ(BufferSyncInterface::PARSE_NO_ERROR, engine()->GetParseError()); +} + + +// Checks that commands in the buffer are properly executed, even if they +// generate a recoverable error. Check that the error status is properly set, +// and reset when queried. +TEST_F(CommandBufferHelperTest, TestRecoverableError) { + CommandBufferEntry args[2]; + args[0].value_uint32 = 3; + args[1].value_float = 4.f; + + // Create a command buffer with 3 commands, 2 of them generating errors + AddCommandWithExpect(BufferSyncInterface::PARSE_NO_ERROR, 1, 2, args); + AddCommandWithExpect(BufferSyncInterface::PARSE_UNKNOWN_COMMAND, 2, 2, args); + AddCommandWithExpect(BufferSyncInterface::PARSE_INVALID_ARGUMENTS, 3, 2, + args); + + helper()->Finish(); + // Check that the commands did happen. + Mock::VerifyAndClearExpectations(api_mock()); + + // Check that the error status was set to the first error. + EXPECT_EQ(BufferSyncInterface::PARSE_UNKNOWN_COMMAND, + engine()->GetParseError()); + // Check that the error status was reset after the query. + EXPECT_EQ(BufferSyncInterface::PARSE_NO_ERROR, engine()->GetParseError()); + + engine()->CloseConnection(); +} + +// Checks that asking for available entries work, and that the parser +// effectively won't use that space. +TEST_F(CommandBufferHelperTest, TestAvailableEntries) { + CommandBufferEntry args[2]; + args[0].value_uint32 = 3; + args[1].value_float = 4.f; + + // Add 2 commands through the helper - 8 entries + AddCommandWithExpect(BufferSyncInterface::PARSE_NO_ERROR, 1, 0, NULL); + AddCommandWithExpect(BufferSyncInterface::PARSE_NO_ERROR, 2, 0, NULL); + AddCommandWithExpect(BufferSyncInterface::PARSE_NO_ERROR, 3, 2, args); + AddCommandWithExpect(BufferSyncInterface::PARSE_NO_ERROR, 4, 2, args); + + // Ask for 5 entries. + helper()->WaitForAvailableEntries(5); + + CommandBufferOffset put = get_helper_put(); + CheckFreeSpace(put, 5); + + // Add more commands. + AddCommandWithExpect(BufferSyncInterface::PARSE_NO_ERROR, 5, 2, args); + + // Wait until everything is done done. + helper()->Finish(); + + // Check that the commands did happen. + Mock::VerifyAndClearExpectations(api_mock()); + + // Check the error status. + ASSERT_EQ(BufferSyncInterface::PARSING, engine()->GetStatus()); + EXPECT_EQ(BufferSyncInterface::PARSE_NO_ERROR, engine()->GetParseError()); +} + +// Checks that the InsertToken/WaitForToken work. +TEST_F(CommandBufferHelperTest, TestToken) { + CommandBufferEntry args[2]; + args[0].value_uint32 = 3; + args[1].value_float = 4.f; + + // Add a first command. + AddCommandWithExpect(BufferSyncInterface::PARSE_NO_ERROR, 3, 2, args); + // keep track of the buffer position. + CommandBufferOffset command1_put = get_helper_put(); + unsigned int token = helper()->InsertToken(); + + EXPECT_CALL(*api_mock(), DoCommand(SET_TOKEN, 1, _)) + .WillOnce(DoAll(Invoke(api_mock(), &AsyncAPIMock::SetToken), + Return(BufferSyncInterface::PARSE_NO_ERROR))); + // Add another command. + AddCommandWithExpect(BufferSyncInterface::PARSE_NO_ERROR, 4, 2, args); + helper()->WaitForToken(token); + // check that the get pointer is beyond the first command. + EXPECT_LE(command1_put, engine()->Get()); + helper()->Finish(); + + // Check that the commands did happen. + Mock::VerifyAndClearExpectations(api_mock()); + + // Check the error status. + ASSERT_EQ(BufferSyncInterface::PARSING, engine()->GetStatus()); + EXPECT_EQ(BufferSyncInterface::PARSE_NO_ERROR, engine()->GetParseError()); +} + +} // namespace command_buffer +} // namespace o3d diff --git a/o3d/command_buffer/client/cross/effect_helper.cc b/o3d/command_buffer/client/cross/effect_helper.cc new file mode 100644 index 0000000..0cab6ee --- /dev/null +++ b/o3d/command_buffer/client/cross/effect_helper.cc @@ -0,0 +1,211 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +// This file implements the EffectHelper class. + +#include "command_buffer/common/cross/cmd_buffer_format.h" +#include "command_buffer/client/cross/cmd_buffer_helper.h" +#include "command_buffer/client/cross/effect_helper.h" +#include "command_buffer/client/cross/fenced_allocator.h" +#include "command_buffer/client/cross/id_allocator.h" + +// TODO: write a unit test. + +namespace o3d { +namespace command_buffer { + +bool EffectHelper::CreateEffectParameters(ResourceID effect_id, + std::vector<EffectParamDesc> *descs) { + using effect_param::Desc; + DCHECK_NE(effect_id, kInvalidResource); + DCHECK(descs); + descs->clear(); + + // Get the param count. + Uint32 *retval = shm_allocator_->AllocTyped<Uint32>(1); + CommandBufferEntry args[4]; + args[0].value_uint32 = effect_id; + args[1].value_uint32 = sizeof(*retval); + args[2].value_uint32 = shm_id_; + args[3].value_uint32 = shm_allocator_->GetOffset(retval); + helper_->AddCommand(GET_PARAM_COUNT, 4, args); + // Finish has to be called to get the result. + helper_->Finish(); + + // We could have failed if the effect_id is invalid. + if (helper_->interface()->GetParseError() != + BufferSyncInterface::PARSE_NO_ERROR) { + shm_allocator_->Free(retval); + return false; + } + unsigned int param_count = *retval; + + shm_allocator_->Free(retval); + unsigned int max_buffer_size = shm_allocator_->GetLargestFreeOrPendingSize(); + if (max_buffer_size < sizeof(Desc)) { // NOLINT + // Not enough memory to get at least 1 param desc. + return false; + } + descs->resize(param_count); + for (unsigned int i = 0; i < param_count; ++i) { + EffectParamDesc *desc = &((*descs)[i]); + desc->id = param_id_allocator_->AllocateID(); + args[0].value_uint32 = desc->id; + args[1].value_uint32 = effect_id; + args[2].value_uint32 = i; + helper_->AddCommand(CREATE_PARAM, 3, args); + } + + // Read param descriptions in batches. We use as much shared memory as + // possible so that we only call Finish as little as possible. + unsigned int max_param_per_batch = + std::min(param_count, max_buffer_size / sizeof(Desc)); // NOLINT + Desc *raw_descs = shm_allocator_->AllocTyped<Desc>(max_param_per_batch); + DCHECK(raw_descs); + for (unsigned int i = 0; i < param_count; i += max_param_per_batch) { + unsigned int count = std::min(param_count - i, max_param_per_batch); + for (unsigned int j = 0 ; j < count; ++j) { + EffectParamDesc *desc = &((*descs)[i + j]); + Desc *raw_desc = raw_descs + j; + args[0].value_uint32 = desc->id; + args[1].value_uint32 = sizeof(*raw_desc); + args[2].value_uint32 = shm_id_; + args[3].value_uint32 = shm_allocator_->GetOffset(raw_desc); + helper_->AddCommand(GET_PARAM_DESC, 4, args); + } + // Finish to get the results. + helper_->Finish(); + DCHECK_EQ(helper_->interface()->GetParseError(), + BufferSyncInterface::PARSE_NO_ERROR); + for (unsigned int j = 0 ; j < count; ++j) { + EffectParamDesc *desc = &((*descs)[i + j]); + Desc *raw_desc = raw_descs + j; + desc->data_type = raw_desc->data_type; + desc->data_size = raw_desc->data_size; + desc->cmd_desc_size = raw_desc->size; + } + } + shm_allocator_->Free(raw_descs); + return true; +} + +bool EffectHelper::GetParamStrings(EffectParamDesc *desc) { + using effect_param::Desc; + DCHECK(desc); + DCHECK_NE(desc->id, kInvalidResource); + // desc may not have come directly from CreateEffectParameters, so it may be + // less than the minimum required size. + unsigned int size = std::max(desc->cmd_desc_size, sizeof(Desc)); // NOLINT + Desc *raw_desc = static_cast<Desc *>(shm_allocator_->Alloc(size)); + if (!raw_desc) { + // Not enough memory to get the param desc. + return false; + } + CommandBufferEntry args[4]; + args[0].value_uint32 = desc->id; + args[1].value_uint32 = size; + args[2].value_uint32 = shm_id_; + args[3].value_uint32 = shm_allocator_->GetOffset(raw_desc); + helper_->AddCommand(GET_PARAM_DESC, 4, args); + // Finish to get the results. + helper_->Finish(); + + // We could have failed if the param ID is invalid. + if (helper_->interface()->GetParseError() != + BufferSyncInterface::PARSE_NO_ERROR) { + shm_allocator_->Free(raw_desc); + return false; + } + + if (raw_desc->size > size) { + // We had not allocated enough memory the first time (e.g. if the + // EffectParamDesc didn't come from CreateEffectParameters, so the user had + // no way of knowing what size was needed for the strings), so re-allocate + // and try again. + size = raw_desc->size; + desc->cmd_desc_size = size; + shm_allocator_->Free(raw_desc); + raw_desc = static_cast<Desc *>(shm_allocator_->Alloc(size)); + if (!raw_desc) { + // Not enough memory to get the param desc. + return false; + } + args[1].value_uint32 = size; + args[3].value_uint32 = shm_allocator_->GetOffset(raw_desc); + helper_->AddCommand(GET_PARAM_DESC, 4, args); + // Finish to get the results. + helper_->Finish(); + DCHECK_EQ(helper_->interface()->GetParseError(), + BufferSyncInterface::PARSE_NO_ERROR); + DCHECK_EQ(raw_desc->size, size); + } + + const char *raw_desc_string = reinterpret_cast<char *>(raw_desc); + if (raw_desc->name_offset) { + DCHECK_LE(raw_desc->name_offset + raw_desc->name_size, raw_desc->size); + DCHECK_GT(raw_desc->name_size, 0); + DCHECK_EQ(raw_desc_string[raw_desc->name_offset + raw_desc->name_size - 1], + 0); + desc->name = String(raw_desc_string + raw_desc->name_offset, + raw_desc->name_size - 1); + } else { + desc->name.clear(); + } + if (raw_desc->semantic_offset) { + DCHECK_LE(raw_desc->semantic_offset + raw_desc->semantic_size, + raw_desc->size); + DCHECK_GT(raw_desc->semantic_size, 0); + DCHECK_EQ(raw_desc_string[raw_desc->semantic_offset + + raw_desc->semantic_size - 1], + 0); + desc->semantic = String(raw_desc_string + raw_desc->semantic_offset, + raw_desc->semantic_size - 1); + } else { + desc->semantic.clear(); + } + shm_allocator_->Free(raw_desc); + return true; +} + +void EffectHelper::DestroyEffectParameters( + const std::vector<EffectParamDesc> &descs) { + CommandBufferEntry args[1]; + for (unsigned int i = 0; i < descs.size(); ++i) { + const EffectParamDesc &desc = descs[i]; + args[0].value_uint32 = desc.id; + helper_->AddCommand(DESTROY_PARAM, 1, args); + param_id_allocator_->FreeID(desc.id); + } +} + +} // namespace command_buffer +} // namespace o3d diff --git a/o3d/command_buffer/client/cross/effect_helper.h b/o3d/command_buffer/client/cross/effect_helper.h new file mode 100644 index 0000000..19523ee --- /dev/null +++ b/o3d/command_buffer/client/cross/effect_helper.h @@ -0,0 +1,134 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +// This file defines the EffectHelper class. + +#ifndef O3D_COMMAND_BUFFER_CLIENT_CROSS_EFFECT_HELPER_H_ +#define O3D_COMMAND_BUFFER_CLIENT_CROSS_EFFECT_HELPER_H_ + +#include <vector> +#include "command_buffer/common/cross/resource.h" + +namespace o3d { +namespace command_buffer { + +class FencedAllocatorWrapper; +class IdAllocator; +class CommandBufferHelper; + +// A helper class to find parameters in an effect. +class EffectHelper { + public: + // A more usable version of effect_param::Desc + struct EffectParamDesc { + ResourceID id; // The resource ID for the param. + String name; // The name of the param. + String semantic; // The semantic of the param. + effect_param::DataType data_type; // The data type of a param. + unsigned int data_size; // The size of the data for a param. + unsigned int cmd_desc_size; // The size of the effect_param::Desc + // structure (counting strings) for a + // param. + }; + + EffectHelper(CommandBufferHelper *helper, + FencedAllocatorWrapper *shm_allocator, + unsigned int shm_id, + IdAllocator *param_id_allocator) + : helper_(helper), + shm_allocator_(shm_allocator), + shm_id_(shm_id), + param_id_allocator_(param_id_allocator) { + DCHECK(helper); + DCHECK(shm_allocator); + DCHECK(param_id_allocator); + } + + // Creates all the parameters in an effect and gets their descriptions. The + // strings will not be retrieved, so name and semantic will be empty. The + // cmd_desc_size field will be set to the proper size to be able to get the + // strings with a single command within GetParamStrings, so it should be left + // alone. + // + // The ResourceIDs will be allocated in the param_id_allocator. + // Temporary buffers will be allocated in the shm_allocator, but they will be + // freed before the function returns (possibly pending a token). At least + // sizeof(effect_param::Desc) must be available for this function to succeed. + // This function will call Finish(), hence will block. + // + // Parameters: + // effect_id: the ResourceID of the effect. + // descs: A pointer to a vector containing the returned descriptions. + // The pointed vector will be cleared. + // Returns: + // true if successful. Reasons for failure are: + // - invalid effect_id, + // - not enough memory in the shm_allocator_. + bool CreateEffectParameters(ResourceID effect_id, + std::vector<EffectParamDesc> *descs); + + // Gets the strings for a desc. This will fill in the values for the name and + // semantic fields. + // Temporary buffers will be allocated in the shm_allocator, but they will be + // freed before the function returns (possibly pending a token). At least + // desc.cmd_desc_size (as returned by CreateEffectParameters) must be + // available for this function to succeed. + // This function will call Finish(), hence will block. + // + // Parameters: + // desc: a pointer to the description for a parameter. The id field should + // be set to the ResourceID of the parameter. + // Returns: + // true if successful. Reasons for failure are: + // - invalid parameter ResourceID, + // - not enough memory in the shm_allocator_. + bool GetParamStrings(EffectParamDesc *desc); + + // Destroys all parameter resources referenced by the descriptions. The + // ResourceID will be freed from the param_id_allocator. + // Parameters: + // descs: the vector of descriptions containing the ResourceIDs of the + // parameters to destroy. + void DestroyEffectParameters(const std::vector<EffectParamDesc> &descs); + private: + CommandBufferHelper *helper_; + FencedAllocatorWrapper *shm_allocator_; + unsigned int shm_id_; + IdAllocator *param_id_allocator_; + + DISALLOW_COPY_AND_ASSIGN(EffectHelper); +}; + +} // namespace command_buffer +} // namespace o3d + +#endif // O3D_COMMAND_BUFFER_CLIENT_CROSS_EFFECT_HELPER_H_ diff --git a/o3d/command_buffer/client/cross/fenced_allocator.cc b/o3d/command_buffer/client/cross/fenced_allocator.cc new file mode 100644 index 0000000..3c66f5d --- /dev/null +++ b/o3d/command_buffer/client/cross/fenced_allocator.cc @@ -0,0 +1,216 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +// This file contains the implementation of the FencedAllocator class. + +#include "command_buffer/client/cross/fenced_allocator.h" +#include <algorithm> +#include "command_buffer/client/cross/cmd_buffer_helper.h" + +namespace o3d { +namespace command_buffer { + +#ifndef COMPILER_MSVC +const FencedAllocator::Offset FencedAllocator::kInvalidOffset; +#endif + +FencedAllocator::~FencedAllocator() { + // Free blocks pending tokens. + for (unsigned int i = 0; i < blocks_.size(); ++i) { + if (blocks_[i].state == FREE_PENDING_TOKEN) { + i = WaitForTokenAndFreeBlock(i); + } + } + DCHECK_EQ(blocks_.size(), 1); + DCHECK_EQ(blocks_[0].state, FREE); +} + +// Looks for a non-allocated block that is big enough. Search in the FREE +// blocks first (for direct usage), first-fit, then in the FREE_PENDING_TOKEN +// blocks, waiting for them. The current implementation isn't smart about +// optimizing what to wait for, just looks inside the block in order (first-fit +// as well). +FencedAllocator::Offset FencedAllocator::Alloc(unsigned int size) { + // Similarly to malloc, an allocation of 0 allocates at least 1 byte, to + // return different pointers every time. + if (size == 0) size = 1; + + // Try first to allocate in a free block. + for (unsigned int i = 0; i < blocks_.size(); ++i) { + Block &block = blocks_[i]; + if (block.state == FREE && block.size >= size) { + return AllocInBlock(i, size); + } + } + + // No free block is available. Look for blocks pending tokens, and wait for + // them to be re-usable. + for (unsigned int i = 0; i < blocks_.size(); ++i) { + if (blocks_[i].state != FREE_PENDING_TOKEN) + continue; + i = WaitForTokenAndFreeBlock(i); + if (blocks_[i].size >= size) + return AllocInBlock(i, size); + } + return kInvalidOffset; +} + +// Looks for the corresponding block, mark it FREE, and collapse it if +// necessary. +void FencedAllocator::Free(FencedAllocator::Offset offset) { + BlockIndex index = GetBlockByOffset(offset); + DCHECK_NE(blocks_[index].state, FREE); + blocks_[index].state = FREE; + CollapseFreeBlock(index); +} + +// Looks for the corresponding block, mark it FREE_PENDING_TOKEN. +void FencedAllocator::FreePendingToken(FencedAllocator::Offset offset, + unsigned int token) { + BlockIndex index = GetBlockByOffset(offset); + Block &block = blocks_[index]; + block.state = FREE_PENDING_TOKEN; + block.token = token; +} + +// Gets the max of the size of the blocks marked as free. +unsigned int FencedAllocator::GetLargestFreeSize() { + unsigned int max_size = 0; + for (unsigned int i = 0; i < blocks_.size(); ++i) { + Block &block = blocks_[i]; + if (block.state == FREE) + max_size = std::max(max_size, block.size); + } + return max_size; +} + +// Gets the size of the largest segment of blocks that are either FREE or +// FREE_PENDING_TOKEN. +unsigned int FencedAllocator::GetLargestFreeOrPendingSize() { + unsigned int max_size = 0; + unsigned int current_size = 0; + for (unsigned int i = 0; i < blocks_.size(); ++i) { + Block &block = blocks_[i]; + if (block.state == IN_USE) { + max_size = std::max(max_size, current_size); + current_size = 0; + } else { + DCHECK(block.state == FREE || block.state == FREE_PENDING_TOKEN); + current_size += block.size; + } + } + return std::max(max_size, current_size); +} + +// Makes sure that: +// - there is at least one block. +// - there are no contiguous FREE blocks (they should have been collapsed). +// - the successive offsets match the block sizes, and they are in order. +bool FencedAllocator::CheckConsistency() { + if (blocks_.size() < 1) return false; + for (unsigned int i = 0; i < blocks_.size() - 1; ++i) { + Block ¤t = blocks_[i]; + Block &next = blocks_[i + 1]; + // This test is NOT included in the next one, because offset is unsigned. + if (next.offset <= current.offset) + return false; + if (next.offset != current.offset + current.size) + return false; + if (current.state == FREE && next.state == FREE) + return false; + } + return true; +} + +// Collapse the block to the next one, then to the previous one. Provided the +// structure is consistent, those are the only blocks eligible for collapse. +FencedAllocator::BlockIndex FencedAllocator::CollapseFreeBlock( + BlockIndex index) { + if (index + 1 < blocks_.size()) { + Block &next = blocks_[index + 1]; + if (next.state == FREE) { + blocks_[index].size += next.size; + blocks_.erase(blocks_.begin() + index + 1); + } + } + if (index > 0) { + Block &prev = blocks_[index - 1]; + if (prev.state == FREE) { + prev.size += blocks_[index].size; + blocks_.erase(blocks_.begin() + index); + --index; + } + } + return index; +} + +// Waits for the block's token, then mark the block as free, then collapse it. +FencedAllocator::BlockIndex FencedAllocator::WaitForTokenAndFreeBlock( + BlockIndex index) { + Block &block = blocks_[index]; + DCHECK_EQ(block.state, FREE_PENDING_TOKEN); + helper_->WaitForToken(block.token); + block.state = FREE; + return CollapseFreeBlock(index); +} + +// If the block is exactly the requested size, simply mark it IN_USE, otherwise +// split it and mark the first one (of the requested size) IN_USE. +FencedAllocator::Offset FencedAllocator::AllocInBlock(BlockIndex index, + unsigned int size) { + Block &block = blocks_[index]; + DCHECK_GE(block.size, size); + DCHECK_EQ(block.state, FREE); + Offset offset = block.offset; + if (block.size == size) { + block.state = IN_USE; + return offset; + } + Block newblock = { FREE, offset + size, block.size - size, kUnusedToken}; + block.state = IN_USE; + block.size = size; + // this is the last thing being done because it may invalidate block; + blocks_.insert(blocks_.begin() + index + 1, newblock); + return offset; +} + +// The blocks are in offset order, so we can do a binary search. +FencedAllocator::BlockIndex FencedAllocator::GetBlockByOffset(Offset offset) { + Block templ = { IN_USE, offset, 0, kUnusedToken }; + Container::iterator it = std::lower_bound(blocks_.begin(), blocks_.end(), + templ, OffsetCmp()); + DCHECK(it != blocks_.end() && it->offset == offset); + return it-blocks_.begin(); +} + +} // namespace command_buffer +} // namespace o3d diff --git a/o3d/command_buffer/client/cross/fenced_allocator.h b/o3d/command_buffer/client/cross/fenced_allocator.h new file mode 100644 index 0000000..d6a4904 --- /dev/null +++ b/o3d/command_buffer/client/cross/fenced_allocator.h @@ -0,0 +1,269 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +// This file contains the definition of the FencedAllocator class. + +#ifndef O3D_COMMAND_BUFFER_CLIENT_CROSS_FENCED_ALLOCATOR_H_ +#define O3D_COMMAND_BUFFER_CLIENT_CROSS_FENCED_ALLOCATOR_H_ + +#include <vector> +#include "base/basictypes.h" +#include "command_buffer/common/cross/logging.h" + +namespace o3d { +namespace command_buffer { +class CommandBufferHelper; + +// FencedAllocator provides a mechanism to manage allocations within a fixed +// block of memory (storing the book-keeping externally). Furthermore this +// class allows to free data "pending" the passage of a command buffer token, +// that is, the memory won't be reused until the command buffer has processed +// that token. +// +// NOTE: Although this class is intended to be used in the command buffer +// environment which is multi-process, this class isn't "thread safe", because +// it isn't meant to be shared across modules. It is thread-compatible though +// (see http://www.corp.google.com/eng/doc/cpp_primer.html#thread_safety). +class FencedAllocator { + public: + typedef unsigned int Offset; + // Invalid offset, returned by Alloc in case of failure. + static const Offset kInvalidOffset = 0xffffffffU; + + // Creates a FencedAllocator. Note that the size of the buffer is passed, but + // not its base address: everything is handled as offsets into the buffer. + FencedAllocator(unsigned int size, + CommandBufferHelper *helper) + : helper_(helper) { + Block block = { FREE, 0, size, kUnusedToken }; + blocks_.push_back(block); + } + + ~FencedAllocator(); + + // Allocates a block of memory. If the buffer is out of directly available + // memory, this function may wait until memory that was freed "pending a + // token" can be re-used. + // + // Parameters: + // size: the size of the memory block to allocate. + // + // Returns: + // the offset of the allocated memory block, or kInvalidOffset if out of + // memory. + Offset Alloc(unsigned int size); + + // Frees a block of memory. + // + // Parameters: + // offset: the offset of the memory block to free. + void Free(Offset offset); + + // Frees a block of memory, pending the passage of a token. That memory won't + // be re-allocated until the token has passed through the command stream. + // + // Parameters: + // offset: the offset of the memory block to free. + // token: the token value to wait for before re-using the memory. + void FreePendingToken(Offset offset, unsigned int token); + + // Gets the size of the largest free block that is available without waiting. + unsigned int GetLargestFreeSize(); + + // Gets the size of the largest free block that can be allocated if the + // caller can wait. Allocating a block of this size will succeed, but may + // block. + unsigned int GetLargestFreeOrPendingSize(); + + // Checks for consistency inside the book-keeping structures. Used for + // testing. + bool CheckConsistency(); + + private: + // Status of a block of memory, for book-keeping. + enum State { + IN_USE, + FREE, + FREE_PENDING_TOKEN + }; + + // Book-keeping sturcture that describes a block of memory. + struct Block { + State state; + Offset offset; + unsigned int size; + unsigned int token; // token to wait for in the FREE_PENDING_TOKEN case. + }; + + // Comparison functor for memory block sorting. + class OffsetCmp { + public: + bool operator() (const Block &left, const Block &right) { + return left.offset < right.offset; + } + }; + + typedef std::vector<Block> Container; + typedef unsigned int BlockIndex; + + static const unsigned int kUnusedToken = 0; + + // Gets the index of a memory block, given its offset. + BlockIndex GetBlockByOffset(Offset offset); + + // Collapse a free block with its neighbours if they are free. Returns the + // index of the collapsed block. + // NOTE: this will invalidate block indices. + BlockIndex CollapseFreeBlock(BlockIndex index); + + // Waits for a FREE_PENDING_TOKEN block to be usable, and free it. Returns + // the new index of that block (since it may have been collapsed). + // NOTE: this will invalidate block indices. + BlockIndex WaitForTokenAndFreeBlock(BlockIndex index); + + // Allocates a block of memory inside a given block, splitting it in two + // (unless that block is of the exact requested size). + // NOTE: this will invalidate block indices. + // Returns the offset of the allocated block (NOTE: this is different from + // the other functions that return a block index). + Offset AllocInBlock(BlockIndex index, unsigned int size); + + command_buffer::CommandBufferHelper *helper_; + Container blocks_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(FencedAllocator); +}; + +// This class functions just like FencedAllocator, but its API uses pointers +// instead of offsets. +class FencedAllocatorWrapper { + public: + FencedAllocatorWrapper(unsigned int size, + CommandBufferHelper *helper, + void *base) + : allocator_(size, helper), + base_(base) { } + + // Allocates a block of memory. If the buffer is out of directly available + // memory, this function may wait until memory that was freed "pending a + // token" can be re-used. + // + // Parameters: + // size: the size of the memory block to allocate. + // + // Returns: + // the pointer to the allocated memory block, or NULL if out of + // memory. + void *Alloc(unsigned int size) { + FencedAllocator::Offset offset = allocator_.Alloc(size); + return GetPointer(offset); + } + + // Allocates a block of memory. If the buffer is out of directly available + // memory, this function may wait until memory that was freed "pending a + // token" can be re-used. + // This is a type-safe version of Alloc, returning a typed pointer. + // + // Parameters: + // count: the number of elements to allocate. + // + // Returns: + // the pointer to the allocated memory block, or NULL if out of + // memory. + template <typename T> T *AllocTyped(unsigned int count) { + return static_cast<T *>(Alloc(count * sizeof(T))); + } + + // Frees a block of memory. + // + // Parameters: + // pointer: the pointer to the memory block to free. + void Free(void *pointer) { + DCHECK(pointer); + allocator_.Free(GetOffset(pointer)); + } + + // Frees a block of memory, pending the passage of a token. That memory won't + // be re-allocated until the token has passed through the command stream. + // + // Parameters: + // pointer: the pointer to the memory block to free. + // token: the token value to wait for before re-using the memory. + void FreePendingToken(void *pointer, unsigned int token) { + DCHECK(pointer); + allocator_.FreePendingToken(GetOffset(pointer), token); + } + + // Gets a pointer to a memory block given the base memory and the offset. + // It translates FencedAllocator::kInvalidOffset to NULL. + void *GetPointer(FencedAllocator::Offset offset) { + return (offset == FencedAllocator::kInvalidOffset) ? + NULL : static_cast<char *>(base_) + offset; + } + + // Gets the offset to a memory block given the base memory and the address. + // It translates NULL to FencedAllocator::kInvalidOffset. + FencedAllocator::Offset GetOffset(void *pointer) { + return pointer ? static_cast<char *>(pointer) - static_cast<char *>(base_) : + FencedAllocator::kInvalidOffset; + } + + // Gets the size of the largest free block that is available without waiting. + unsigned int GetLargestFreeSize() { + return allocator_.GetLargestFreeSize(); + } + + // Gets the size of the largest free block that can be allocated if the + // caller can wait. + unsigned int GetLargestFreeOrPendingSize() { + return allocator_.GetLargestFreeOrPendingSize(); + } + + // Checks for consistency inside the book-keeping structures. Used for + // testing. + bool CheckConsistency() { + return allocator_.CheckConsistency(); + } + + FencedAllocator &allocator() { return allocator_; } + + private: + FencedAllocator allocator_; + void *base_; + DISALLOW_IMPLICIT_CONSTRUCTORS(FencedAllocatorWrapper); +}; + +} // namespace command_buffer +} // namespace o3d + + +#endif // O3D_COMMAND_BUFFER_CLIENT_CROSS_FENCED_ALLOCATOR_H_ diff --git a/o3d/command_buffer/client/cross/fenced_allocator_test.cc b/o3d/command_buffer/client/cross/fenced_allocator_test.cc new file mode 100644 index 0000000..93dedca --- /dev/null +++ b/o3d/command_buffer/client/cross/fenced_allocator_test.cc @@ -0,0 +1,498 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +// This file contains the tests for the FencedAllocator class. + +#include "tests/common/win/testing_common.h" +#include "command_buffer/client/cross/cmd_buffer_helper.h" +#include "command_buffer/client/cross/fenced_allocator.h" +#include "command_buffer/service/cross/cmd_buffer_engine.h" +#include "command_buffer/service/cross/mocks.h" + +namespace o3d { +namespace command_buffer { +using testing::Return; +using testing::Mock; +using testing::Truly; +using testing::Sequence; +using testing::DoAll; +using testing::Invoke; +using testing::_; + +// Test fixture for FencedAllocator test - Creates a FencedAllocator, using a +// CommandBufferHelper with a mock AsyncAPIInterface for its interface (calling +// it directly, not through the RPC mechanism), making sure NOOPs are ignored +// and SET_TOKEN are properly forwarded to the engine. +class FencedAllocatorTest : public testing::Test { + public: + static const unsigned int kBufferSize = 1024; + + protected: + virtual void SetUp() { + api_mock_.reset(new AsyncAPIMock); + // ignore noops in the mock - we don't want to inspect the internals of the + // helper. + EXPECT_CALL(*api_mock_, DoCommand(NOOP, 0, _)) + .WillRepeatedly(Return(BufferSyncInterface::PARSE_NO_ERROR)); + // Forward the SetToken calls to the engine + EXPECT_CALL(*api_mock(), DoCommand(SET_TOKEN, 1, _)) + .WillRepeatedly(DoAll(Invoke(api_mock(), &AsyncAPIMock::SetToken), + Return(BufferSyncInterface::PARSE_NO_ERROR))); + engine_.reset(new CommandBufferEngine(api_mock_.get())); + api_mock_->set_engine(engine_.get()); + + nacl::SocketAddress client_address = { "test-socket" }; + client_socket_ = nacl::BoundSocket(&client_address); + engine_->InitConnection(); + helper_.reset(new CommandBufferHelper(engine_.get())); + helper_->Init(100); + + allocator_.reset(new FencedAllocator(kBufferSize, helper_.get())); + } + + virtual void TearDown() { + EXPECT_TRUE(allocator_->CheckConsistency()); + allocator_.release(); + helper_.release(); + engine_->CloseConnection(); + nacl::Close(client_socket_); + } + + CommandBufferHelper *helper() { return helper_.get(); } + CommandBufferEngine *engine() { return engine_.get(); } + AsyncAPIMock *api_mock() { return api_mock_.get(); } + FencedAllocator *allocator() { return allocator_.get(); } + private: + scoped_ptr<AsyncAPIMock> api_mock_; + scoped_ptr<CommandBufferEngine> engine_; + scoped_ptr<CommandBufferHelper> helper_; + scoped_ptr<FencedAllocator> allocator_; + nacl::Handle client_socket_; +}; + +#ifndef COMPILER_MSVC +const unsigned int FencedAllocatorTest::kBufferSize; +#endif + +// Checks basic alloc and free. +TEST_F(FencedAllocatorTest, TestBasic) { + allocator()->CheckConsistency(); + + const unsigned int kSize = 16; + FencedAllocator::Offset offset = allocator()->Alloc(kSize); + EXPECT_NE(FencedAllocator::kInvalidOffset, offset); + EXPECT_GE(kBufferSize, offset+kSize); + EXPECT_TRUE(allocator()->CheckConsistency()); + + allocator()->Free(offset); + EXPECT_TRUE(allocator()->CheckConsistency()); +} + +// Checks out-of-memory condition. +TEST_F(FencedAllocatorTest, TestOutOfMemory) { + EXPECT_TRUE(allocator()->CheckConsistency()); + + const unsigned int kSize = 16; + const unsigned int kAllocCount = kBufferSize / kSize; + CHECK(kAllocCount * kSize == kBufferSize); + + // Allocate several buffers to fill in the memory. + FencedAllocator::Offset offsets[kAllocCount]; + for (unsigned int i = 0; i < kAllocCount; ++i) { + offsets[i] = allocator()->Alloc(kSize); + EXPECT_NE(FencedAllocator::kInvalidOffset, offsets[i]); + EXPECT_GE(kBufferSize, offsets[i]+kSize); + EXPECT_TRUE(allocator()->CheckConsistency()); + } + + // This allocation should fail. + FencedAllocator::Offset offset_failed = allocator()->Alloc(kSize); + EXPECT_EQ(FencedAllocator::kInvalidOffset, offset_failed); + EXPECT_TRUE(allocator()->CheckConsistency()); + + // Free one successful allocation, reallocate with half the size + allocator()->Free(offsets[0]); + EXPECT_TRUE(allocator()->CheckConsistency()); + offsets[0] = allocator()->Alloc(kSize/2); + EXPECT_NE(FencedAllocator::kInvalidOffset, offsets[0]); + EXPECT_GE(kBufferSize, offsets[0]+kSize); + EXPECT_TRUE(allocator()->CheckConsistency()); + + // This allocation should fail as well. + offset_failed = allocator()->Alloc(kSize); + EXPECT_EQ(FencedAllocator::kInvalidOffset, offset_failed); + EXPECT_TRUE(allocator()->CheckConsistency()); + + // Free up everything. + for (unsigned int i = 0; i < kAllocCount; ++i) { + allocator()->Free(offsets[i]); + EXPECT_TRUE(allocator()->CheckConsistency()); + } +} + +// Checks the free-pending-token mechanism. +TEST_F(FencedAllocatorTest, TestFreePendingToken) { + EXPECT_TRUE(allocator()->CheckConsistency()); + + const unsigned int kSize = 16; + const unsigned int kAllocCount = kBufferSize / kSize; + CHECK(kAllocCount * kSize == kBufferSize); + + // Allocate several buffers to fill in the memory. + FencedAllocator::Offset offsets[kAllocCount]; + for (unsigned int i = 0; i < kAllocCount; ++i) { + offsets[i] = allocator()->Alloc(kSize); + EXPECT_NE(FencedAllocator::kInvalidOffset, offsets[i]); + EXPECT_GE(kBufferSize, offsets[i]+kSize); + EXPECT_TRUE(allocator()->CheckConsistency()); + } + + // This allocation should fail. + FencedAllocator::Offset offset_failed = allocator()->Alloc(kSize); + EXPECT_EQ(FencedAllocator::kInvalidOffset, offset_failed); + EXPECT_TRUE(allocator()->CheckConsistency()); + + // Free one successful allocation, pending fence. + unsigned int token = helper()->InsertToken(); + allocator()->FreePendingToken(offsets[0], token); + EXPECT_TRUE(allocator()->CheckConsistency()); + + // The way we hooked up the helper and engine, it won't process commands + // until it has to wait for something. Which means the token shouldn't have + // passed yet at this point. + EXPECT_GT(token, engine()->GetToken()); + + // This allocation will need to reclaim the space freed above, so that should + // process the commands until the token is passed. + offsets[0] = allocator()->Alloc(kSize); + EXPECT_NE(FencedAllocator::kInvalidOffset, offsets[0]); + EXPECT_GE(kBufferSize, offsets[0]+kSize); + EXPECT_TRUE(allocator()->CheckConsistency()); + // Check that the token has indeed passed. + EXPECT_LE(token, engine()->GetToken()); + + // Free up everything. + for (unsigned int i = 0; i < kAllocCount; ++i) { + allocator()->Free(offsets[i]); + EXPECT_TRUE(allocator()->CheckConsistency()); + } +} + +// Tests GetLargestFreeSize +TEST_F(FencedAllocatorTest, TestGetLargestFreeSize) { + EXPECT_TRUE(allocator()->CheckConsistency()); + EXPECT_EQ(kBufferSize, allocator()->GetLargestFreeSize()); + + FencedAllocator::Offset offset = allocator()->Alloc(kBufferSize); + ASSERT_NE(FencedAllocator::kInvalidOffset, offset); + EXPECT_EQ(0, allocator()->GetLargestFreeSize()); + allocator()->Free(offset); + EXPECT_EQ(kBufferSize, allocator()->GetLargestFreeSize()); + + const unsigned int kSize = 16; + offset = allocator()->Alloc(kSize); + ASSERT_NE(FencedAllocator::kInvalidOffset, offset); + // The following checks that the buffer is allocated "smartly" - which is + // dependent on the implementation. But both first-fit or best-fit would + // ensure that. + EXPECT_EQ(kBufferSize - kSize, allocator()->GetLargestFreeSize()); + + // Allocate 2 more buffers (now 3), and then free the first two. This is to + // ensure a hole. Note that this is dependent on the first-fit current + // implementation. + FencedAllocator::Offset offset1 = allocator()->Alloc(kSize); + ASSERT_NE(FencedAllocator::kInvalidOffset, offset1); + FencedAllocator::Offset offset2 = allocator()->Alloc(kSize); + ASSERT_NE(FencedAllocator::kInvalidOffset, offset2); + allocator()->Free(offset); + allocator()->Free(offset1); + EXPECT_EQ(kBufferSize - 3 * kSize, allocator()->GetLargestFreeSize()); + + offset = allocator()->Alloc(kBufferSize - 3 * kSize); + ASSERT_NE(FencedAllocator::kInvalidOffset, offset); + EXPECT_EQ(2 * kSize, allocator()->GetLargestFreeSize()); + + offset1 = allocator()->Alloc(2 * kSize); + ASSERT_NE(FencedAllocator::kInvalidOffset, offset1); + EXPECT_EQ(0, allocator()->GetLargestFreeSize()); + + allocator()->Free(offset); + allocator()->Free(offset1); + allocator()->Free(offset2); +} + +// Tests GetLargestFreeOrPendingSize +TEST_F(FencedAllocatorTest, TestGetLargestFreeOrPendingSize) { + EXPECT_TRUE(allocator()->CheckConsistency()); + EXPECT_EQ(kBufferSize, allocator()->GetLargestFreeOrPendingSize()); + + FencedAllocator::Offset offset = allocator()->Alloc(kBufferSize); + ASSERT_NE(FencedAllocator::kInvalidOffset, offset); + EXPECT_EQ(0, allocator()->GetLargestFreeOrPendingSize()); + allocator()->Free(offset); + EXPECT_EQ(kBufferSize, allocator()->GetLargestFreeOrPendingSize()); + + const unsigned int kSize = 16; + offset = allocator()->Alloc(kSize); + ASSERT_NE(FencedAllocator::kInvalidOffset, offset); + // The following checks that the buffer is allocates "smartly" - which is + // dependent on the implementation. But both first-fit or best-fit would + // ensure that. + EXPECT_EQ(kBufferSize - kSize, allocator()->GetLargestFreeOrPendingSize()); + + // Allocate 2 more buffers (now 3), and then free the first two. This is to + // ensure a hole. Note that this is dependent on the first-fit current + // implementation. + FencedAllocator::Offset offset1 = allocator()->Alloc(kSize); + ASSERT_NE(FencedAllocator::kInvalidOffset, offset1); + FencedAllocator::Offset offset2 = allocator()->Alloc(kSize); + ASSERT_NE(FencedAllocator::kInvalidOffset, offset2); + allocator()->Free(offset); + allocator()->Free(offset1); + EXPECT_EQ(kBufferSize - 3 * kSize, + allocator()->GetLargestFreeOrPendingSize()); + + // Free the last one, pending a token. + unsigned int token = helper()->InsertToken(); + allocator()->FreePendingToken(offset2, token); + + // Now all the buffers have been freed... + EXPECT_EQ(kBufferSize, allocator()->GetLargestFreeOrPendingSize()); + // .. but one is still waiting for the token. + EXPECT_EQ(kBufferSize - 3 * kSize, + allocator()->GetLargestFreeSize()); + + // The way we hooked up the helper and engine, it won't process commands + // until it has to wait for something. Which means the token shouldn't have + // passed yet at this point. + EXPECT_GT(token, engine()->GetToken()); + // This allocation will need to reclaim the space freed above, so that should + // process the commands until the token is passed, but it will succeed. + offset = allocator()->Alloc(kBufferSize); + ASSERT_NE(FencedAllocator::kInvalidOffset, offset); + // Check that the token has indeed passed. + EXPECT_LE(token, engine()->GetToken()); + allocator()->Free(offset); + + // Everything now has been freed... + EXPECT_EQ(kBufferSize, allocator()->GetLargestFreeOrPendingSize()); + // ... for real. + EXPECT_EQ(kBufferSize, allocator()->GetLargestFreeSize()); +} + +// Test fixture for FencedAllocatorWrapper test - Creates a +// FencedAllocatorWrapper, using a CommandBufferHelper with a mock +// AsyncAPIInterface for its interface (calling it directly, not through the +// RPC mechanism), making sure NOOPs are ignored and SET_TOKEN are properly +// forwarded to the engine. +class FencedAllocatorWrapperTest : public testing::Test { + public: + static const unsigned int kBufferSize = 1024; + + protected: + virtual void SetUp() { + api_mock_.reset(new AsyncAPIMock); + // ignore noops in the mock - we don't want to inspect the internals of the + // helper. + EXPECT_CALL(*api_mock_, DoCommand(NOOP, 0, _)) + .WillRepeatedly(Return(BufferSyncInterface::PARSE_NO_ERROR)); + // Forward the SetToken calls to the engine + EXPECT_CALL(*api_mock(), DoCommand(SET_TOKEN, 1, _)) + .WillRepeatedly(DoAll(Invoke(api_mock(), &AsyncAPIMock::SetToken), + Return(BufferSyncInterface::PARSE_NO_ERROR))); + engine_.reset(new CommandBufferEngine(api_mock_.get())); + api_mock_->set_engine(engine_.get()); + + nacl::SocketAddress client_address = { "test-socket" }; + client_socket_ = nacl::BoundSocket(&client_address); + engine_->InitConnection(); + helper_.reset(new CommandBufferHelper(engine_.get())); + helper_->Init(100); + + // Though allocating this buffer isn't strictly necessary, it makes + // allocations point to valid addresses, so they could be used for + // something. + buffer_.reset(new char[kBufferSize]); + allocator_.reset(new FencedAllocatorWrapper(kBufferSize, helper_.get(), + base())); + } + + virtual void TearDown() { + EXPECT_TRUE(allocator_->CheckConsistency()); + allocator_.release(); + buffer_.release(); + helper_.release(); + engine_->CloseConnection(); + nacl::Close(client_socket_); + } + + CommandBufferHelper *helper() { return helper_.get(); } + CommandBufferEngine *engine() { return engine_.get(); } + AsyncAPIMock *api_mock() { return api_mock_.get(); } + FencedAllocatorWrapper *allocator() { return allocator_.get(); } + char *base() { return buffer_.get(); } + private: + scoped_ptr<AsyncAPIMock> api_mock_; + scoped_ptr<CommandBufferEngine> engine_; + scoped_ptr<CommandBufferHelper> helper_; + scoped_ptr<FencedAllocatorWrapper> allocator_; + scoped_array<char> buffer_; + nacl::Handle client_socket_; +}; + +#ifndef COMPILER_MSVC +const unsigned int FencedAllocatorWrapperTest::kBufferSize; +#endif + +// Checks basic alloc and free. +TEST_F(FencedAllocatorWrapperTest, TestBasic) { + allocator()->CheckConsistency(); + + const unsigned int kSize = 16; + void *pointer = allocator()->Alloc(kSize); + ASSERT_TRUE(pointer); + EXPECT_LE(base(), static_cast<char *>(pointer)); + EXPECT_GE(kBufferSize, static_cast<char *>(pointer) - base() + kSize); + EXPECT_TRUE(allocator()->CheckConsistency()); + + allocator()->Free(pointer); + EXPECT_TRUE(allocator()->CheckConsistency()); + + char *pointer_char = allocator()->AllocTyped<char>(kSize); + ASSERT_TRUE(pointer_char); + EXPECT_LE(base(), pointer_char); + EXPECT_GE(base() + kBufferSize, pointer_char + kSize); + allocator()->Free(pointer_char); + EXPECT_TRUE(allocator()->CheckConsistency()); + + unsigned int *pointer_uint = allocator()->AllocTyped<unsigned int>(kSize); + ASSERT_TRUE(pointer_uint); + EXPECT_LE(base(), reinterpret_cast<char *>(pointer_uint)); + EXPECT_GE(base() + kBufferSize, + reinterpret_cast<char *>(pointer_uint + kSize)); + + // Check that it did allocate kSize * sizeof(unsigned int). We can't tell + // directly, except from the remaining size. + EXPECT_EQ(kBufferSize - kSize * sizeof(*pointer_uint), + allocator()->GetLargestFreeSize()); + allocator()->Free(pointer_uint); +} + +// Checks out-of-memory condition. +TEST_F(FencedAllocatorWrapperTest, TestOutOfMemory) { + allocator()->CheckConsistency(); + + const unsigned int kSize = 16; + const unsigned int kAllocCount = kBufferSize / kSize; + CHECK(kAllocCount * kSize == kBufferSize); + + // Allocate several buffers to fill in the memory. + void *pointers[kAllocCount]; + for (unsigned int i = 0; i < kAllocCount; ++i) { + pointers[i] = allocator()->Alloc(kSize); + EXPECT_TRUE(pointers[i]); + EXPECT_TRUE(allocator()->CheckConsistency()); + } + + // This allocation should fail. + void *pointer_failed = allocator()->Alloc(kSize); + EXPECT_FALSE(pointer_failed); + EXPECT_TRUE(allocator()->CheckConsistency()); + + // Free one successful allocation, reallocate with half the size + allocator()->Free(pointers[0]); + EXPECT_TRUE(allocator()->CheckConsistency()); + pointers[0] = allocator()->Alloc(kSize/2); + EXPECT_TRUE(pointers[0]); + EXPECT_TRUE(allocator()->CheckConsistency()); + + // This allocation should fail as well. + pointer_failed = allocator()->Alloc(kSize); + EXPECT_FALSE(pointer_failed); + EXPECT_TRUE(allocator()->CheckConsistency()); + + // Free up everything. + for (unsigned int i = 0; i < kAllocCount; ++i) { + allocator()->Free(pointers[i]); + EXPECT_TRUE(allocator()->CheckConsistency()); + } +} + +// Checks the free-pending-token mechanism. +TEST_F(FencedAllocatorWrapperTest, TestFreePendingToken) { + allocator()->CheckConsistency(); + + const unsigned int kSize = 16; + const unsigned int kAllocCount = kBufferSize / kSize; + CHECK(kAllocCount * kSize == kBufferSize); + + // Allocate several buffers to fill in the memory. + void *pointers[kAllocCount]; + for (unsigned int i = 0; i < kAllocCount; ++i) { + pointers[i] = allocator()->Alloc(kSize); + EXPECT_TRUE(pointers[i]); + EXPECT_TRUE(allocator()->CheckConsistency()); + } + + // This allocation should fail. + void *pointer_failed = allocator()->Alloc(kSize); + EXPECT_FALSE(pointer_failed); + EXPECT_TRUE(allocator()->CheckConsistency()); + + // Free one successful allocation, pending fence. + unsigned int token = helper()->InsertToken(); + allocator()->FreePendingToken(pointers[0], token); + EXPECT_TRUE(allocator()->CheckConsistency()); + + // The way we hooked up the helper and engine, it won't process commands + // until it has to wait for something. Which means the token shouldn't have + // passed yet at this point. + EXPECT_GT(token, engine()->GetToken()); + + // This allocation will need to reclaim the space freed above, so that should + // process the commands until the token is passed. + pointers[0] = allocator()->Alloc(kSize); + EXPECT_TRUE(pointers[0]); + EXPECT_TRUE(allocator()->CheckConsistency()); + // Check that the token has indeed passed. + EXPECT_LE(token, engine()->GetToken()); + + // Free up everything. + for (unsigned int i = 0; i < kAllocCount; ++i) { + allocator()->Free(pointers[i]); + EXPECT_TRUE(allocator()->CheckConsistency()); + } +} + + +} // namespace command_buffer +} // namespace o3d diff --git a/o3d/command_buffer/client/cross/id_allocator.cc b/o3d/command_buffer/client/cross/id_allocator.cc new file mode 100644 index 0000000..e13604f --- /dev/null +++ b/o3d/command_buffer/client/cross/id_allocator.cc @@ -0,0 +1,87 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +// This file contains the implementation of IdAllocator. + +#include "command_buffer/client/cross/id_allocator.h" + +namespace o3d { +namespace command_buffer { + +IdAllocator::IdAllocator() : bitmap_(1) { bitmap_[0] = 0; } + +static const unsigned int kBitsPerUint32 = sizeof(Uint32) * 8; // NOLINT + +// Looks for the first non-full entry, and return the first free bit in that +// entry. If all the entries are full, it will return the first bit of an entry +// that would be appended, but doesn't actually append that entry to the vector. +unsigned int IdAllocator::FindFirstFree() const { + size_t size = bitmap_.size(); + for (unsigned int i = 0; i < size; ++i) { + Uint32 value = bitmap_[i]; + if (value != 0xffffffffU) { + for (unsigned int j = 0; j < kBitsPerUint32; ++j) { + if (!(value & (1 << j))) return i * kBitsPerUint32 + j; + } + DLOG(FATAL) << "Code should not reach here."; + } + } + return size*kBitsPerUint32; +} + +// Sets the correct bit in the proper entry, resizing the vector if needed. +void IdAllocator::SetBit(unsigned int bit, bool value) { + size_t size = bitmap_.size(); + if (bit >= size * kBitsPerUint32) { + size_t newsize = bit / kBitsPerUint32 + 1; + bitmap_.resize(newsize); + for (size_t i = size; i < newsize; ++i) bitmap_[i] = 0; + } + Uint32 mask = 1U << (bit % kBitsPerUint32); + if (value) { + bitmap_[bit / kBitsPerUint32] |= mask; + } else { + bitmap_[bit / kBitsPerUint32] &= ~mask; + } +} + +// Gets the bit from the proper entry. This doesn't resize the vector, just +// returns false if the bit is beyond the last entry. +bool IdAllocator::GetBit(unsigned int bit) const { + size_t size = bitmap_.size(); + if (bit / kBitsPerUint32 >= size) return false; + Uint32 mask = 1U << (bit % kBitsPerUint32); + return (bitmap_[bit / kBitsPerUint32] & mask) != 0; +} + +} // namespace command_buffer +} // namespace o3d diff --git a/o3d/command_buffer/client/cross/id_allocator.h b/o3d/command_buffer/client/cross/id_allocator.h new file mode 100644 index 0000000..26fe1f5 --- /dev/null +++ b/o3d/command_buffer/client/cross/id_allocator.h @@ -0,0 +1,80 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +// This file contains the definition of the IdAllocator class. + +#ifndef O3D_COMMAND_BUFFER_CLIENT_CROSS_ID_ALLOCATOR_H_ +#define O3D_COMMAND_BUFFER_CLIENT_CROSS_ID_ALLOCATOR_H_ + +#include <vector> +#include "base/basictypes.h" +#include "command_buffer/common/cross/types.h" +#include "command_buffer/common/cross/resource.h" + +namespace o3d { +namespace command_buffer { + +// A class to manage the allocation of resource IDs. It uses a bitfield stored +// into a vector of unsigned ints. +class IdAllocator { + public: + IdAllocator(); + + // Allocates a new resource ID. + command_buffer::ResourceID AllocateID() { + unsigned int bit = FindFirstFree(); + SetBit(bit, true); + return bit; + } + + // Frees a resource ID. + void FreeID(command_buffer::ResourceID id) { + SetBit(id, false); + } + + // Checks whether or not a resource ID is in use. + bool InUse(command_buffer::ResourceID id) { + return GetBit(id); + } + private: + void SetBit(unsigned int bit, bool value); + bool GetBit(unsigned int bit) const; + unsigned int FindFirstFree() const; + + std::vector<Uint32> bitmap_; + DISALLOW_COPY_AND_ASSIGN(IdAllocator); +}; + +} // namespace command_buffer +} // namespace o3d + +#endif // O3D_COMMAND_BUFFER_CLIENT_CROSS_ID_ALLOCATOR_H_ diff --git a/o3d/command_buffer/client/cross/id_allocator_test.cc b/o3d/command_buffer/client/cross/id_allocator_test.cc new file mode 100644 index 0000000..3cc2674 --- /dev/null +++ b/o3d/command_buffer/client/cross/id_allocator_test.cc @@ -0,0 +1,114 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +// This file has the unit tests for the IdAllocator class. + +#include "tests/common/win/testing_common.h" +#include "command_buffer/client/cross/id_allocator.h" + +namespace o3d { +namespace command_buffer { + +using command_buffer::ResourceID; + +class IdAllocatorTest : public testing::Test { + protected: + virtual void SetUp() {} + virtual void TearDown() {} + + IdAllocator* id_allocator() { return &id_allocator_; } + + private: + IdAllocator id_allocator_; +}; + +// Checks basic functionality: AllocateID, FreeID, InUse. +TEST_F(IdAllocatorTest, TestBasic) { + IdAllocator *allocator = id_allocator(); + // Check that resource 0 is not in use + EXPECT_FALSE(allocator->InUse(0)); + + // Allocate an ID, check that it's in use. + ResourceID id1 = allocator->AllocateID(); + EXPECT_TRUE(allocator->InUse(id1)); + + // Allocate another ID, check that it's in use, and different from the first + // one. + ResourceID id2 = allocator->AllocateID(); + EXPECT_TRUE(allocator->InUse(id2)); + EXPECT_NE(id1, id2); + + // Free one of the IDs, check that it's not in use any more. + allocator->FreeID(id1); + EXPECT_FALSE(allocator->InUse(id1)); + + // Frees the other ID, check that it's not in use any more. + allocator->FreeID(id2); + EXPECT_FALSE(allocator->InUse(id2)); +} + +// Checks that the resource IDs are allocated conservatively, and re-used after +// being freed. +TEST_F(IdAllocatorTest, TestAdvanced) { + IdAllocator *allocator = id_allocator(); + + // Allocate a significant number of resources. + const int kNumResources = 100; + ResourceID ids[kNumResources]; + for (int i = 0; i < kNumResources; ++i) { + ids[i] = allocator->AllocateID(); + EXPECT_TRUE(allocator->InUse(ids[i])); + } + + // Check that the allocation is conservative with resource IDs, that is that + // the resource IDs don't go over kNumResources - so that the service doesn't + // have to allocate too many internal structures when the resources are used. + for (int i = 0; i < kNumResources; ++i) { + EXPECT_GT(kNumResources, ids[i]); + } + + // Check that the next resources are still free. + for (int i = 0; i < kNumResources; ++i) { + EXPECT_FALSE(allocator->InUse(kNumResources + i)); + } + + // Check that a new allocation re-uses the resource we just freed. + ResourceID id1 = ids[kNumResources / 2]; + allocator->FreeID(id1); + EXPECT_FALSE(allocator->InUse(id1)); + ResourceID id2 = allocator->AllocateID(); + EXPECT_TRUE(allocator->InUse(id2)); + EXPECT_EQ(id1, id2); +} + +} // namespace command_buffer +} // namespace o3d |