|  | // Copyright 2016 The Cobalt Authors. All Rights Reserved. | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //     http://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | #include "starboard/blitter.h" | 
|  | #include "starboard/event.h" | 
|  | #include "starboard/log.h" | 
|  | #include "starboard/system.h" | 
|  | #include "starboard/thread.h" | 
|  | #include "starboard/time.h" | 
|  | #include "starboard/window.h" | 
|  |  | 
|  | #if SB_HAS(BLITTER) | 
|  |  | 
|  | class Application { | 
|  | public: | 
|  | Application(); | 
|  | ~Application(); | 
|  |  | 
|  | private: | 
|  | // The callback function passed to SbEventSchedule().  Its purpose is to | 
|  | // forward to the non-static RenderScene() method. | 
|  | static void RenderSceneEventCallback(void* param); | 
|  |  | 
|  | // Renders one frame of the animated scene, incrementing |frame_| each time | 
|  | // it is called. | 
|  | void RenderScene(); | 
|  |  | 
|  | static Application* application_; | 
|  |  | 
|  | // The current frame we are rendering, initialized to 0 and incremented after | 
|  | // each frame. | 
|  | int frame_; | 
|  |  | 
|  | // The SbWindow within which we will perform our rendering. | 
|  | SbWindow window_; | 
|  |  | 
|  | // The blitting device we will be targeting. | 
|  | SbBlitterDevice device_; | 
|  |  | 
|  | // The swap chain that represents |window_|'s display. | 
|  | SbBlitterSwapChain swap_chain_; | 
|  | static const int kOutputWidth = 1920; | 
|  | static const int kOutputHeight = 1080; | 
|  |  | 
|  | // The surface of a simple unchanging RGBA bitmap image we will use while | 
|  | // rendering. | 
|  | SbBlitterSurface rgba_image_surface_; | 
|  | // The surface of a simple unchanging alpha-only bitmap image we will use | 
|  | // while rendering. | 
|  | SbBlitterSurface alpha_image_surface_; | 
|  | static const int kImageWidth = 100; | 
|  | static const int kImageHeight = 100; | 
|  |  | 
|  | // An offscreen surface that we will render to and render from each frame. | 
|  | SbBlitterSurface offscreen_surface_; | 
|  | static const int kOffscreenWidth = 400; | 
|  | static const int kOffscreenHeight = 400; | 
|  |  | 
|  | // The context within which we will issue all our draw commands. | 
|  | SbBlitterContext context_; | 
|  | }; | 
|  |  | 
|  | namespace { | 
|  | // Populates a region of memory designated as pixel data with a gradient texture | 
|  | // that includes transparent alpha. | 
|  | void FillGradientImageData(int width, | 
|  | int height, | 
|  | int pitch_in_bytes, | 
|  | void* pixel_data) { | 
|  | uint8_t* pixels = static_cast<uint8_t*>(pixel_data); | 
|  |  | 
|  | for (int y = 0; y < height; ++y) { | 
|  | for (int x = 0; x < width; ++x) { | 
|  | // Setup a vertical gradient in the alpha color channel. | 
|  | float alpha = static_cast<float>(y) / height; | 
|  | uint8_t alpha_byte = static_cast<uint8_t>(alpha) * 255; | 
|  |  | 
|  | // Setup a horizontal gradient in the green color channel. | 
|  | uint8_t green_byte = | 
|  | static_cast<uint8_t>((alpha * static_cast<float>(x) / width) * 255); | 
|  |  | 
|  | // Assuming BGRA color format. | 
|  | pixels[x * 4 + 0] = 0; | 
|  | pixels[x * 4 + 1] = green_byte; | 
|  | pixels[x * 4 + 0] = 0; | 
|  | pixels[x * 4 + 3] = alpha_byte; | 
|  | } | 
|  |  | 
|  | pixels += pitch_in_bytes; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Populates a region of memory designated as pixel data with a checker texture | 
|  | // using an alpha-only pixel format. | 
|  | void FillAlphaCheckerImageData(int width, | 
|  | int height, | 
|  | int pitch, | 
|  | void* pixel_data) { | 
|  | uint8_t* pixels = static_cast<uint8_t*>(pixel_data); | 
|  |  | 
|  | for (int y = 0; y < height; ++y) { | 
|  | for (int x = 0; x < width; ++x) { | 
|  | bool is_first_vertical_half = y <= height / 2; | 
|  | bool is_first_horizontal_half = x <= width / 2; | 
|  |  | 
|  | uint8_t color = | 
|  | is_first_horizontal_half ^ is_first_vertical_half ? 255 : 0; | 
|  |  | 
|  | pixels[x + y * pitch] = color; | 
|  | } | 
|  | } | 
|  | } | 
|  | }  // namespace | 
|  |  | 
|  | Application::Application() { | 
|  | frame_ = 0; | 
|  | // In order to access most of the Blitter API, we'll need to create a SbWindow | 
|  | // object whose display we can have the Blitter API target. | 
|  | SbWindowOptions options; | 
|  | SbWindowSetDefaultOptions(&options); | 
|  | options.size.width = 1920; | 
|  | options.size.height = 1080; | 
|  | window_ = SbWindowCreate(NULL); | 
|  | SB_CHECK(SbWindowIsValid(window_)); | 
|  |  | 
|  | // We start by constructing a SbBlitterDevice which represents the connection | 
|  | // to the hardware blitting device (e.g. a GPU). | 
|  | device_ = SbBlitterCreateDefaultDevice(); | 
|  |  | 
|  | // Creating the swap chain associates our blitting device to the target | 
|  | // window's output display. | 
|  | swap_chain_ = SbBlitterCreateSwapChainFromWindow(device_, window_); | 
|  |  | 
|  | // Let's setup a texture.  We start by creating a SbBlitterPixelData object | 
|  | // which can be populated with pixel data by the CPU and then passed on to | 
|  | // the device for reference within blit calls. | 
|  | SbBlitterPixelData image_data = SbBlitterCreatePixelData( | 
|  | device_, kImageWidth, kImageHeight, kSbBlitterPixelDataFormatBGRA8); | 
|  |  | 
|  | // Once our pixel data object is created, we can extract from it the image | 
|  | // data pitch as well as a CPU-accessible pointer to the pixel data.  We | 
|  | // pass this information into a function to populate the image. | 
|  | int image_data_pitch = SbBlitterGetPixelDataPitchInBytes(image_data); | 
|  | FillGradientImageData(kImageHeight, kImageHeight, image_data_pitch, | 
|  | SbBlitterGetPixelDataPointer(image_data)); | 
|  |  | 
|  | // Now that our pixel data is finalized, we create a surface from it.  After | 
|  | // this call, our SbBlitterPixelData object is invalid (i.e. it is transformed | 
|  | // into a SbBlitterSurface object). | 
|  | rgba_image_surface_ = | 
|  | SbBlitterCreateSurfaceFromPixelData(device_, image_data); | 
|  |  | 
|  | // Now setup our alpha-only image. | 
|  | SbBlitterPixelData alpha_image_data = SbBlitterCreatePixelData( | 
|  | device_, kImageWidth, kImageHeight, kSbBlitterPixelDataFormatA8); | 
|  | int alpha_image_data_pitch = | 
|  | SbBlitterGetPixelDataPitchInBytes(alpha_image_data); | 
|  | FillAlphaCheckerImageData(kImageHeight, kImageHeight, alpha_image_data_pitch, | 
|  | SbBlitterGetPixelDataPointer(alpha_image_data)); | 
|  | alpha_image_surface_ = | 
|  | SbBlitterCreateSurfaceFromPixelData(device_, alpha_image_data); | 
|  |  | 
|  | // We will also create a (initially blank) surface for use as an offscreen | 
|  | // render target. | 
|  | offscreen_surface_ = SbBlitterCreateRenderTargetSurface( | 
|  | device_, kOffscreenWidth, kOffscreenHeight, kSbBlitterSurfaceFormatRGBA8); | 
|  |  | 
|  | // Finally, in order to issue draw calls, we need a context that maintains | 
|  | // draw state for us. | 
|  | context_ = SbBlitterCreateContext(device_); | 
|  |  | 
|  | RenderScene(); | 
|  | } | 
|  |  | 
|  | Application::~Application() { | 
|  | // Cleanup all used resources. | 
|  | SbBlitterDestroyContext(context_); | 
|  | SbBlitterDestroySurface(offscreen_surface_); | 
|  | SbBlitterDestroySurface(alpha_image_surface_); | 
|  | SbBlitterDestroySurface(rgba_image_surface_); | 
|  | SbBlitterDestroySwapChain(swap_chain_); | 
|  | SbBlitterDestroyDevice(device_); | 
|  | SbWindowDestroy(window_); | 
|  | } | 
|  |  | 
|  | void Application::RenderSceneEventCallback(void* param) { | 
|  | // Forward the call to the application instance specified as the parameter. | 
|  | Application* application = static_cast<Application*>(param); | 
|  | application->RenderScene(); | 
|  | } | 
|  |  | 
|  | void Application::RenderScene() { | 
|  | // Setup our animation parameter that follows a sawtooth pattern. | 
|  | int frame_mod_255 = frame_ % 255; | 
|  |  | 
|  | // We can get a render target from a swap chain, so that we can target the | 
|  | // display with draw calls. | 
|  | SbBlitterRenderTarget primary_render_target = | 
|  | SbBlitterGetRenderTargetFromSwapChain(swap_chain_); | 
|  |  | 
|  | // Surfaces created for use as a render target provide both a | 
|  | // SbBlitterRenderTarget and SbBlitterTexture objects, for use as target and | 
|  | // source in draw calls, respectively. | 
|  | SbBlitterRenderTarget offscreen_render_target = | 
|  | SbBlitterGetRenderTargetFromSurface(offscreen_surface_); | 
|  |  | 
|  | // First we set the render target to our offscreen surface. | 
|  | SbBlitterSetRenderTarget(context_, offscreen_render_target); | 
|  |  | 
|  | // And enable alpha blending. | 
|  | SbBlitterSetBlending(context_, true); | 
|  |  | 
|  | // We start by clearing our entire surface to a animating shade of red. | 
|  | SbBlitterSetColor(context_, SbBlitterColorFromRGBA(frame_mod_255, 0, 0, 255)); | 
|  | SbBlitterFillRect(context_, | 
|  | SbBlitterMakeRect(0, 0, kOffscreenWidth, kOffscreenHeight)); | 
|  | // We then draw a green rectangle in the top left corner. | 
|  | SbBlitterSetColor(context_, SbBlitterColorFromRGBA(0, 255, 0, 32)); | 
|  | SbBlitterFillRect(context_, SbBlitterMakeRect(50, 50, 100, 100)); | 
|  |  | 
|  | // Now we disable blending for the next few draw calls, resulting in their | 
|  | // alpha channels replacing the alpha channels that already exist in the | 
|  | // render target. | 
|  | SbBlitterSetBlending(context_, false); | 
|  |  | 
|  | // Punch a blue half-transparent rectangle out in the top right corner. | 
|  | SbBlitterSetColor(context_, SbBlitterColorFromRGBA(0, 0, 255, 128)); | 
|  | SbBlitterFillRect(context_, SbBlitterMakeRect(250, 50, 100, 100)); | 
|  |  | 
|  | // Render our surface to the offscreen surface as well, stretched | 
|  | // horizontally, in two different locations. | 
|  | SbBlitterSetBlending(context_, true); | 
|  | SbBlitterSetModulateBlitsWithColor(context_, false); | 
|  | SbBlitterBlitRectToRect(context_, rgba_image_surface_, | 
|  | SbBlitterMakeRect(0, 0, kImageWidth, kImageHeight), | 
|  | SbBlitterMakeRect(frame_mod_255, frame_mod_255, | 
|  | kImageWidth * 2, kImageHeight)); | 
|  |  | 
|  | SbBlitterSetModulateBlitsWithColor(context_, true); | 
|  | SbBlitterSetColor(context_, SbBlitterColorFromRGBA(255, 255, 255, 128)); | 
|  | SbBlitterBlitRectToRect(context_, rgba_image_surface_, | 
|  | SbBlitterMakeRect(0, 0, kImageWidth, kImageHeight), | 
|  | SbBlitterMakeRect(frame_mod_255, frame_mod_255 + 100, | 
|  | kImageWidth * 2, kImageHeight)); | 
|  |  | 
|  | // Blit out our alpha checker surface in the color blue. | 
|  | SbBlitterSetColor(context_, SbBlitterColorFromRGBA(0, 0, 255, 255)); | 
|  | SbBlitterBlitRectToRect( | 
|  | context_, alpha_image_surface_, | 
|  | SbBlitterMakeRect(0, 0, kImageWidth, kImageHeight), | 
|  | SbBlitterMakeRect(50, 200, kImageWidth, kImageHeight)); | 
|  |  | 
|  | // Now switch to the primary display render target. | 
|  | SbBlitterSetRenderTarget(context_, primary_render_target); | 
|  |  | 
|  | // Clear the display to an animating shade of green. | 
|  | SbBlitterSetColor(context_, SbBlitterColorFromRGBA(0, frame_mod_255, 0, 255)); | 
|  | SbBlitterFillRect(context_, | 
|  | SbBlitterMakeRect(0, 0, kOutputWidth, kOutputHeight)); | 
|  |  | 
|  | // Render our offscreen surface to the display in three different places | 
|  | // and sizes. | 
|  | SbBlitterSetColor(context_, | 
|  | SbBlitterColorFromRGBA(255, 255, 255, frame_mod_255)); | 
|  | SbBlitterSetModulateBlitsWithColor(context_, true); | 
|  | SbBlitterBlitRectToRect( | 
|  | context_, offscreen_surface_, | 
|  | SbBlitterMakeRect(0, 0, kOffscreenWidth, kOffscreenHeight), | 
|  | SbBlitterMakeRect(10, 10, 200, 200)); | 
|  | SbBlitterBlitRectToRect( | 
|  | context_, offscreen_surface_, | 
|  | SbBlitterMakeRect(0, 0, kOffscreenWidth, kOffscreenHeight), | 
|  | SbBlitterMakeRect(300, 10, 400, 400)); | 
|  | SbBlitterBlitRectToRect( | 
|  | context_, offscreen_surface_, | 
|  | SbBlitterMakeRect(0, 0, kOffscreenWidth, kOffscreenHeight), | 
|  | SbBlitterMakeRect(10, 500, 800, 200)); | 
|  |  | 
|  | SbBlitterSetModulateBlitsWithColor(context_, false); | 
|  |  | 
|  | // Blit an animated tiling of the offscreen surface. | 
|  | SbBlitterBlitRectToRectTiled( | 
|  | context_, offscreen_surface_, | 
|  | SbBlitterMakeRect( | 
|  | frame_mod_255 * 3, frame_mod_255 * 5, | 
|  | 1 + static_cast<int>(kOffscreenWidth * (frame_mod_255 / 31.0f)), | 
|  | 1 + static_cast<int>(kOffscreenWidth * (frame_mod_255 / 63.0f))), | 
|  | SbBlitterMakeRect(900, 100, 400, 400)); | 
|  |  | 
|  | // Blit a batch of 4 instances of the offscreen surface in one draw call. | 
|  | const int kNumBatchRects = 4; | 
|  | SbBlitterRect src_rects[kNumBatchRects]; | 
|  | SbBlitterRect dst_rects[kNumBatchRects]; | 
|  | for (int j = 0; j < kNumBatchRects; ++j) { | 
|  | src_rects[j].x = 0; | 
|  | src_rects[j].y = 0; | 
|  | src_rects[j].width = kOffscreenWidth; | 
|  | src_rects[j].height = kOffscreenHeight; | 
|  |  | 
|  | dst_rects[j].x = 900 + j * 220; | 
|  | dst_rects[j].y = 600; | 
|  | dst_rects[j].width = 200; | 
|  | dst_rects[j].height = 300; | 
|  | } | 
|  | SbBlitterBlitRectsToRects(context_, offscreen_surface_, src_rects, dst_rects, | 
|  | kNumBatchRects); | 
|  |  | 
|  | // Ensure that all draw commands issued to the context are flushed to | 
|  | // the device and guaranteed to eventually be processed. | 
|  | SbBlitterFlushContext(context_); | 
|  |  | 
|  | // Flip the swap chain to reveal our masterpiece to the user. | 
|  | SbBlitterFlipSwapChain(swap_chain_); | 
|  | ++frame_; | 
|  |  | 
|  | // Schedule another frame render ASAP. | 
|  | SbEventSchedule(&Application::RenderSceneEventCallback, this, 0); | 
|  | } | 
|  |  | 
|  | Application* s_application = NULL; | 
|  |  | 
|  | // Simple Starboard window event handling to kick off our blitter application. | 
|  | void SbEventHandle(const SbEvent* event) { | 
|  | switch (event->type) { | 
|  | case kSbEventTypeStart: { | 
|  | // Create the application, after which it will use SbEventSchedule() | 
|  | // on itself to trigger a frame update until the application is | 
|  | // terminated. | 
|  | s_application = new Application(); | 
|  | } break; | 
|  |  | 
|  | case kSbEventTypeStop: { | 
|  | // Shutdown the application. | 
|  | delete s_application; | 
|  | } break; | 
|  |  | 
|  | default: {} | 
|  | } | 
|  | } | 
|  |  | 
|  | #else  // SB_HAS(BLITTER) | 
|  |  | 
|  | void SbEventHandle(const SbEvent* event) { | 
|  | switch (event->type) { | 
|  | case kSbEventTypeStart: { | 
|  | SB_LOG(ERROR) | 
|  | << "Starboard Blitter API is not available on this platform."; | 
|  |  | 
|  | SbSystemRequestStop(0); | 
|  | } break; | 
|  |  | 
|  | default: {} | 
|  | } | 
|  | } | 
|  |  | 
|  | #endif  // SB_HAS(BLITTER) |