1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <stdio.h>
6#include <stdlib.h>
7
8#include <algorithm>
9#include <cassert>
10
11#include "ppapi/c/ppb_gamepad.h"
12#include "ppapi/cpp/graphics_2d.h"
13#include "ppapi/cpp/image_data.h"
14#include "ppapi/cpp/instance.h"
15#include "ppapi/cpp/rect.h"
16#include "ppapi/cpp/size.h"
17#include "ppapi/cpp/var.h"
18#include "ppapi/utility/completion_callback_factory.h"
19
20#ifdef WIN32
21#undef min
22#undef max
23
24// Allow 'this' in initializer list
25#pragma warning(disable : 4355)
26#endif
27
28class GamepadInstance : public pp::Instance {
29 public:
30  explicit GamepadInstance(PP_Instance instance);
31  virtual ~GamepadInstance();
32
33  // Update the graphics context to the new size, and regenerate |pixel_buffer_|
34  // to fit the new size as well.
35  virtual void DidChangeView(const pp::View& view);
36
37  // Flushes its contents of |pixel_buffer_| to the 2D graphics context.
38  void Paint();
39
40  int width() const {
41    return pixel_buffer_ ? pixel_buffer_->size().width() : 0;
42  }
43  int height() const {
44    return pixel_buffer_ ? pixel_buffer_->size().height() : 0;
45  }
46
47  // Indicate whether a flush is pending.  This can only be called from the
48  // main thread; it is not thread safe.
49  bool flush_pending() const { return flush_pending_; }
50  void set_flush_pending(bool flag) { flush_pending_ = flag; }
51
52 private:
53  // Create and initialize the 2D context used for drawing.
54  void CreateContext(const pp::Size& size);
55  // Destroy the 2D drawing context.
56  void DestroyContext();
57  // Push the pixels to the browser, then attempt to flush the 2D context.  If
58  // there is a pending flush on the 2D context, then update the pixels only
59  // and do not flush.
60  void FlushPixelBuffer();
61
62  void FlushCallback(int32_t result);
63
64  bool IsContextValid() const { return graphics_2d_context_ != NULL; }
65
66  pp::CompletionCallbackFactory<GamepadInstance> callback_factory_;
67  pp::Graphics2D* graphics_2d_context_;
68  pp::ImageData* pixel_buffer_;
69  const PPB_Gamepad* gamepad_;
70  bool flush_pending_;
71};
72
73GamepadInstance::GamepadInstance(PP_Instance instance)
74    : pp::Instance(instance),
75      callback_factory_(this),
76      graphics_2d_context_(NULL),
77      pixel_buffer_(NULL),
78      flush_pending_(false) {
79  pp::Module* module = pp::Module::Get();
80  assert(module);
81  gamepad_ = static_cast<const PPB_Gamepad*>(
82      module->GetBrowserInterface(PPB_GAMEPAD_INTERFACE));
83  assert(gamepad_);
84}
85
86GamepadInstance::~GamepadInstance() {
87  DestroyContext();
88  delete pixel_buffer_;
89}
90
91void GamepadInstance::DidChangeView(const pp::View& view) {
92  pp::Rect position = view.GetRect();
93  if (position.size().width() == width() &&
94      position.size().height() == height())
95    return;  // Size didn't change, no need to update anything.
96
97  // Create a new device context with the new size.
98  DestroyContext();
99  CreateContext(position.size());
100  // Delete the old pixel buffer and create a new one.
101  delete pixel_buffer_;
102  pixel_buffer_ = NULL;
103  if (graphics_2d_context_ != NULL) {
104    pixel_buffer_ = new pp::ImageData(this,
105                                      PP_IMAGEDATAFORMAT_BGRA_PREMUL,
106                                      graphics_2d_context_->size(),
107                                      false);
108  }
109  Paint();
110}
111
112void FillRect(pp::ImageData* image,
113              int left,
114              int top,
115              int width,
116              int height,
117              uint32_t color) {
118  for (int y = std::max(0, top);
119       y < std::min(image->size().height() - 1, top + height);
120       y++) {
121    for (int x = std::max(0, left);
122         x < std::min(image->size().width() - 1, left + width);
123         x++)
124      *image->GetAddr32(pp::Point(x, y)) = color;
125  }
126}
127
128void GamepadInstance::Paint() {
129  // Clear the background.
130  FillRect(pixel_buffer_, 0, 0, width(), height(), 0xfff0f0f0);
131
132  // Get current gamepad data.
133  PP_GamepadsSampleData gamepad_data;
134  gamepad_->Sample(pp_instance(), &gamepad_data);
135
136  // Draw the current state for each connected gamepad.
137  for (size_t p = 0; p < gamepad_data.length; ++p) {
138    int width2 = width() / gamepad_data.length / 2;
139    int height2 = height() / 2;
140    int offset = width2 * 2 * p;
141    PP_GamepadSampleData& pad = gamepad_data.items[p];
142
143    if (!pad.connected)
144      continue;
145
146    // Draw axes.
147    for (size_t i = 0; i < pad.axes_length; i += 2) {
148      int x = static_cast<int>(pad.axes[i + 0] * width2 + width2) + offset;
149      int y = static_cast<int>(pad.axes[i + 1] * height2 + height2);
150      uint32_t box_bgra = 0x80000000;  // Alpha 50%.
151      FillRect(pixel_buffer_, x - 3, y - 3, 7, 7, box_bgra);
152    }
153
154    // Draw buttons.
155    for (size_t i = 0; i < pad.buttons_length; ++i) {
156      float button_val = pad.buttons[i];
157      uint32_t colour = static_cast<uint32_t>((button_val * 192) + 63) << 24;
158      int x = i * 8 + 10 + offset;
159      int y = 10;
160      FillRect(pixel_buffer_, x - 3, y - 3, 7, 7, colour);
161    }
162  }
163
164  // Output to the screen.
165  FlushPixelBuffer();
166}
167
168void GamepadInstance::CreateContext(const pp::Size& size) {
169  if (IsContextValid())
170    return;
171  graphics_2d_context_ = new pp::Graphics2D(this, size, false);
172  if (!BindGraphics(*graphics_2d_context_)) {
173    printf("Couldn't bind the device context\n");
174  }
175}
176
177void GamepadInstance::DestroyContext() {
178  if (!IsContextValid())
179    return;
180  delete graphics_2d_context_;
181  graphics_2d_context_ = NULL;
182}
183
184void GamepadInstance::FlushPixelBuffer() {
185  if (!IsContextValid())
186    return;
187  // Note that the pixel lock is held while the buffer is copied into the
188  // device context and then flushed.
189  graphics_2d_context_->PaintImageData(*pixel_buffer_, pp::Point());
190  if (flush_pending())
191    return;
192  set_flush_pending(true);
193  graphics_2d_context_->Flush(
194      callback_factory_.NewCallback(&GamepadInstance::FlushCallback));
195}
196
197void GamepadInstance::FlushCallback(int32_t result) {
198  set_flush_pending(false);
199  Paint();
200}
201
202class GamepadModule : public pp::Module {
203 public:
204  GamepadModule() : pp::Module() {}
205  virtual ~GamepadModule() {}
206
207  virtual pp::Instance* CreateInstance(PP_Instance instance) {
208    return new GamepadInstance(instance);
209  }
210};
211
212namespace pp {
213Module* CreateModule() { return new GamepadModule(); }
214}  // namespace pp
215