| // Copyright 2016 Google Inc. 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) |