1/* Copyright (c) 2013 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
6#include <assert.h>
7#include <math.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11
12#include "ppapi/c/pp_resource.h"
13#include "ppapi/c/ppb_core.h"
14#include "ppapi/c/ppb_fullscreen.h"
15#include "ppapi/c/ppb_graphics_2d.h"
16#include "ppapi/c/ppb_image_data.h"
17#include "ppapi/c/ppb_input_event.h"
18#include "ppapi/c/ppb_instance.h"
19#include "ppapi/c/ppb_view.h"
20
21#include "ppapi_simple/ps_event.h"
22#include "ppapi_simple/ps_main.h"
23
24PPB_Core* g_pCore;
25PPB_Fullscreen* g_pFullscreen;
26PPB_Graphics2D* g_pGraphics2D;
27PPB_ImageData* g_pImageData;
28PPB_Instance* g_pInstance;
29PPB_View* g_pView;
30PPB_InputEvent* g_pInputEvent;
31PPB_KeyboardInputEvent* g_pKeyboardInput;
32PPB_MouseInputEvent* g_pMouseInput;
33PPB_TouchInputEvent* g_pTouchInput;
34
35struct {
36  PP_Resource ctx;
37  struct PP_Size size;
38  int bound;
39  uint8_t* cell_in;
40  uint8_t* cell_out;
41} g_Context;
42
43
44const unsigned int kInitialRandSeed = 0xC0DE533D;
45
46/* BGRA helper macro, for constructing a pixel for a BGRA buffer. */
47#define MakeBGRA(b, g, r, a)  \
48  (((a) << 24) | ((r) << 16) | ((g) << 8) | (b))
49
50
51/*
52 * Convert a count value into a live (green) or dead color value.
53 */
54const uint32_t kNeighborColors[] = {
55    MakeBGRA(0x00, 0x00, 0x00, 0xFF),
56    MakeBGRA(0x00, 0x00, 0x00, 0xFF),
57    MakeBGRA(0x00, 0x00, 0x00, 0xFF),
58    MakeBGRA(0x00, 0x00, 0x00, 0xFF),
59    MakeBGRA(0x00, 0x00, 0x00, 0xFF),
60    MakeBGRA(0x00, 0xFF, 0x00, 0xFF),
61    MakeBGRA(0x00, 0xFF, 0x00, 0xFF),
62    MakeBGRA(0x00, 0xFF, 0x00, 0xFF),
63    MakeBGRA(0x00, 0x00, 0x00, 0xFF),
64    MakeBGRA(0x00, 0x00, 0x00, 0xFF),
65    MakeBGRA(0x00, 0x00, 0x00, 0xFF),
66    MakeBGRA(0x00, 0x00, 0x00, 0xFF),
67    MakeBGRA(0x00, 0x00, 0x00, 0xFF),
68    MakeBGRA(0x00, 0x00, 0x00, 0xFF),
69    MakeBGRA(0x00, 0x00, 0x00, 0xFF),
70    MakeBGRA(0x00, 0x00, 0x00, 0xFF),
71    MakeBGRA(0x00, 0x00, 0x00, 0xFF),
72    MakeBGRA(0x00, 0x00, 0x00, 0xFF),
73};
74
75/*
76 * These represent the new health value of a cell based on its neighboring
77 * values.  The health is binary: either alive or dead.
78 */
79const uint8_t kIsAlive[] = {
80      0, 0, 0, 0, 0, 1, 1, 1, 0,
81      0, 0, 0, 0, 0, 0, 0, 0, 0
82};
83
84void UpdateContext(uint32_t width, uint32_t height) {
85  if (width != g_Context.size.width || height != g_Context.size.height) {
86    size_t size = width * height;
87    size_t index;
88
89    free(g_Context.cell_in);
90    free(g_Context.cell_out);
91
92    /* Create a new context */
93    g_Context.cell_in = (uint8_t*) malloc(size);
94    g_Context.cell_out = (uint8_t*) malloc(size);
95
96    memset(g_Context.cell_out, 0, size);
97    for (index = 0; index < size; index++) {
98      g_Context.cell_in[index] = rand() & 1;
99    }
100  }
101
102  /* Recreate the graphics context on a view change */
103  g_pCore->ReleaseResource(g_Context.ctx);
104  g_Context.size.width = width;
105  g_Context.size.height = height;
106  g_Context.ctx =
107      g_pGraphics2D->Create(PSGetInstanceId(), &g_Context.size, PP_TRUE);
108  g_Context.bound =
109      g_pInstance->BindGraphics(PSGetInstanceId(), g_Context.ctx);
110}
111
112void DrawCell(int32_t x, int32_t y) {
113  int32_t width = g_Context.size.width;
114  int32_t height = g_Context.size.height;
115
116  if (!g_Context.cell_in) return;
117
118  if (x > 0 && x < width - 1 && y > 0 && y < height - 1) {
119    g_Context.cell_in[x - 1 + y * width] = 1;
120    g_Context.cell_in[x + 1 + y * width] = 1;
121    g_Context.cell_in[x + (y - 1) * width] = 1;
122    g_Context.cell_in[x + (y + 1) * width] = 1;
123  }
124}
125
126void ProcessTouchEvent(PSEvent* event) {
127  uint32_t count = g_pTouchInput->GetTouchCount(event->as_resource,
128      PP_TOUCHLIST_TYPE_TOUCHES);
129  uint32_t i, j;
130  for (i = 0; i < count; i++) {
131    struct PP_TouchPoint touch = g_pTouchInput->GetTouchByIndex(
132        event->as_resource, PP_TOUCHLIST_TYPE_TOUCHES, i);
133    int radius = (int)touch.radius.x;
134    int x = (int)touch.position.x;
135    int y = (int)touch.position.y;
136    /* num = 1/100th the area of touch point */
137    int num = (int)(M_PI * radius * radius / 100.0f);
138    for (j = 0; j < num; j++) {
139      int dx = rand() % (radius * 2) - radius;
140      int dy = rand() % (radius * 2) - radius;
141      /* only plot random cells within the touch area */
142      if (dx * dx + dy * dy <= radius * radius)
143        DrawCell(x + dx, y + dy);
144    }
145  }
146}
147
148void ProcessEvent(PSEvent* event) {
149  switch(event->type) {
150    /* If the view updates, build a new Graphics 2D Context */
151    case PSE_INSTANCE_DIDCHANGEVIEW: {
152      struct PP_Rect rect;
153
154      g_pView->GetRect(event->as_resource, &rect);
155      UpdateContext(rect.size.width, rect.size.height);
156      break;
157    }
158
159    case PSE_INSTANCE_HANDLEINPUT: {
160      PP_InputEvent_Type type = g_pInputEvent->GetType(event->as_resource);
161      PP_InputEvent_Modifier modifiers =
162          g_pInputEvent->GetModifiers(event->as_resource);
163
164      switch(type) {
165        case PP_INPUTEVENT_TYPE_MOUSEDOWN:
166        case PP_INPUTEVENT_TYPE_MOUSEMOVE: {
167          struct PP_Point location =
168              g_pMouseInput->GetPosition(event->as_resource);
169          /* If the button is down, draw */
170          if (modifiers & PP_INPUTEVENT_MODIFIER_LEFTBUTTONDOWN) {
171            DrawCell(location.x, location.y);
172          }
173          break;
174        }
175
176        case PP_INPUTEVENT_TYPE_TOUCHSTART:
177        case PP_INPUTEVENT_TYPE_TOUCHMOVE:
178          ProcessTouchEvent(event);
179          break;
180
181        case PP_INPUTEVENT_TYPE_KEYDOWN: {
182          PP_Bool fullscreen = g_pFullscreen->IsFullscreen(PSGetInstanceId());
183          g_pFullscreen->SetFullscreen(PSGetInstanceId(),
184                                       fullscreen ? PP_FALSE : PP_TRUE);
185          break;
186        }
187
188        default:
189          break;
190      }
191      /* case PSE_INSTANCE_HANDLEINPUT */
192      break;
193    }
194
195    default:
196      break;
197  }
198}
199
200
201void Stir(uint32_t width, uint32_t height) {
202  int i;
203  if (g_Context.cell_in == NULL || g_Context.cell_out == NULL)
204    return;
205
206  for (i = 0; i < width; ++i) {
207    g_Context.cell_in[i] = rand() & 1;
208    g_Context.cell_in[i + (height - 1) * width] = rand() & 1;
209  }
210  for (i = 0; i < height; ++i) {
211    g_Context.cell_in[i * width] = rand() & 1;
212    g_Context.cell_in[i * width + (width - 1)] = rand() & 1;
213  }
214}
215
216void Render() {
217  struct PP_Size* psize = &g_Context.size;
218  PP_ImageDataFormat format = PP_IMAGEDATAFORMAT_BGRA_PREMUL;
219
220  /*
221   * Create a buffer to draw into.  Since we are waiting until the next flush
222   * chrome has an opportunity to cache this buffer see ppb_graphics_2d.h.
223   */
224  PP_Resource image =
225      g_pImageData->Create(PSGetInstanceId(), format, psize, PP_FALSE);
226  uint8_t* pixels = g_pImageData->Map(image);
227
228  struct PP_ImageDataDesc desc;
229  uint8_t* cell_temp;
230  uint32_t x, y;
231
232  /* If we somehow have not allocated these pointers yet, skip this frame. */
233  if (!g_Context.cell_in || !g_Context.cell_out) return;
234
235  /* Get the stride. */
236  g_pImageData->Describe(image, &desc);
237
238  /* Stir up the edges to prevent the simulation from reaching steady state. */
239  Stir(desc.size.width, desc.size.height);
240
241  /* Do neighbor summation; apply rules, output pixel color. */
242  for (y = 1; y < desc.size.height - 1; ++y) {
243    uint8_t *src0 = (g_Context.cell_in + (y - 1) * desc.size.width) + 1;
244    uint8_t *src1 = src0 + desc.size.width;
245    uint8_t *src2 = src1 + desc.size.width;
246    int count;
247    uint32_t color;
248    uint8_t *dst = (g_Context.cell_out + y * desc.size.width) + 1;
249    uint32_t *pixel_line =  (uint32_t*) (pixels + y * desc.stride);
250
251    for (x = 1; x < (desc.size.width - 1); ++x) {
252      /* Jitter and sum neighbors. */
253      count = src0[-1] + src0[0] + src0[1] +
254              src1[-1] +         + src1[1] +
255              src2[-1] + src2[0] + src2[1];
256      /* Include center cell. */
257      count = count + count + src1[0];
258      /* Use table lookup indexed by count to determine pixel & alive state. */
259      color = kNeighborColors[count];
260      *pixel_line++ = color;
261      *dst++ = kIsAlive[count];
262      ++src0;
263      ++src1;
264      ++src2;
265    }
266  }
267
268  cell_temp = g_Context.cell_in;
269  g_Context.cell_in = g_Context.cell_out;
270  g_Context.cell_out = cell_temp;
271
272  /* Unmap the range, we no longer need it. */
273  g_pImageData->Unmap(image);
274
275  /* Replace the contexts, and block until it's on the screen. */
276  g_pGraphics2D->ReplaceContents(g_Context.ctx, image);
277  g_pGraphics2D->Flush(g_Context.ctx, PP_BlockUntilComplete());
278
279  /* Release the image data, we no longer need it. */
280  g_pCore->ReleaseResource(image);
281}
282
283/*
284 * Starting point for the module.  We do not use main since it would
285 * collide with main in libppapi_cpp.
286 */
287int example_main(int argc, char *argv[]) {
288  fprintf(stdout,"Started main.\n");
289  g_pCore = (PPB_Core*)PSGetInterface(PPB_CORE_INTERFACE);
290  g_pFullscreen = (PPB_Fullscreen*)PSGetInterface(PPB_FULLSCREEN_INTERFACE);
291  g_pGraphics2D = (PPB_Graphics2D*)PSGetInterface(PPB_GRAPHICS_2D_INTERFACE);
292  g_pInstance = (PPB_Instance*)PSGetInterface(PPB_INSTANCE_INTERFACE);
293  g_pImageData = (PPB_ImageData*)PSGetInterface(PPB_IMAGEDATA_INTERFACE);
294  g_pView = (PPB_View*)PSGetInterface(PPB_VIEW_INTERFACE);
295
296  g_pInputEvent =
297      (PPB_InputEvent*) PSGetInterface(PPB_INPUT_EVENT_INTERFACE);
298  g_pKeyboardInput = (PPB_KeyboardInputEvent*)
299      PSGetInterface(PPB_KEYBOARD_INPUT_EVENT_INTERFACE);
300  g_pMouseInput =
301      (PPB_MouseInputEvent*) PSGetInterface(PPB_MOUSE_INPUT_EVENT_INTERFACE);
302  g_pTouchInput =
303      (PPB_TouchInputEvent*) PSGetInterface(PPB_TOUCH_INPUT_EVENT_INTERFACE);
304
305  PSEventSetFilter(PSE_ALL);
306  while (1) {
307    /* Process all waiting events without blocking */
308    PSEvent* event;
309    while ((event = PSEventTryAcquire()) != NULL) {
310      ProcessEvent(event);
311      PSEventRelease(event);
312    }
313
314    /* Render a frame, blocking until complete. */
315    if (g_Context.bound) {
316      Render();
317    }
318  }
319  return 0;
320}
321
322/*
323 * Register the function to call once the Instance Object is initialized.
324 * see: pappi_simple/ps_main.h
325 */
326PPAPI_SIMPLE_REGISTER_MAIN(example_main);
327