blob: e2edc053db6591f238f3798ac92f45658fc158a4 [file] [log] [blame]
// Copyright 2023 The Pigweed Authors
//
// 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
//
// https://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 <cstdint>
#define PW_LOG_LEVEL PW_LOG_LEVEL_DEBUG
#include "app_common/common.h"
#include "graphics/surface.hpp"
#include "pw_assert/assert.h"
#include "pw_assert/check.h"
#include "pw_board_led/led.h"
#include "pw_color/color.h"
#include "pw_color/colors_endesga32.h"
#include "pw_color/colors_pico8.h"
#include "pw_display/display.h"
#include "pw_framebuffer/framebuffer.h"
#include "pw_log/log.h"
#include "pw_ring_buffer/prefixed_entry_ring_buffer.h"
#include "pw_spin_delay/delay.h"
#include "pw_string/string_builder.h"
#include "pw_sys_io/sys_io.h"
#include "random.h"
#if defined(USE_FREERTOS)
#include "FreeRTOS.h"
#include "task.h"
#endif // if defined(USE_FREERTOS)
using pw::color::color_rgb565_t;
using pw::color::colors_pico8_rgb565;
using pw::display::Display;
using pw::framebuffer::Framebuffer;
using pw::ring_buffer::PrefixedEntryRingBuffer;
// TODO(tonymd): move this code into a pre_init section (i.e. boot.cc) which
// is part of the target. Not all targets currently have this.
#if defined(DEFINE_FREERTOS_MEMORY_FUNCTIONS)
std::array<StackType_t, 100 /*configMINIMAL_STACK_SIZE*/> freertos_idle_stack;
StaticTask_t freertos_idle_tcb;
std::array<StackType_t, configTIMER_TASK_STACK_DEPTH> freertos_timer_stack;
StaticTask_t freertos_timer_tcb;
extern "C" {
// Required for configUSE_TIMERS.
void vApplicationGetTimerTaskMemory(StaticTask_t** ppxTimerTaskTCBBuffer,
StackType_t** ppxTimerTaskStackBuffer,
uint32_t* pulTimerTaskStackSize) {
*ppxTimerTaskTCBBuffer = &freertos_timer_tcb;
*ppxTimerTaskStackBuffer = freertos_timer_stack.data();
*pulTimerTaskStackSize = freertos_timer_stack.size();
}
void vApplicationGetIdleTaskMemory(StaticTask_t** ppxIdleTaskTCBBuffer,
StackType_t** ppxIdleTaskStackBuffer,
uint32_t* pulIdleTaskStackSize) {
*ppxIdleTaskTCBBuffer = &freertos_idle_tcb;
*ppxIdleTaskStackBuffer = freertos_idle_stack.data();
*pulIdleTaskStackSize = freertos_idle_stack.size();
}
} // extern "C"
#endif // defined(DEFINE_FREERTOS_MEMORY_FUNCTIONS)
namespace {
#if defined(USE_FREERTOS)
std::array<StackType_t, configMINIMAL_STACK_SIZE> s_freertos_stack;
StaticTask_t s_freertos_tcb;
#endif // defined(USE_FREERTOS)
struct test_particle {
blit::Vec2 pos;
blit::Vec2 vel;
int age;
bool generated = false;
};
void rain_generate(test_particle& p, blit::Surface screen) {
p.pos = blit::Vec2(GetRandomFloat(screen.bounds.w),
GetRandomFloat(10) - (screen.bounds.h + 10));
p.vel = blit::Vec2(0, 150);
p.age = 0;
p.generated = true;
};
void rain(blit::Surface screen, uint32_t time_ms, blit::Rect floor_position) {
static test_particle s[300];
static int generate_index = 0;
static uint32_t last_time_ms = time_ms;
int elapsed_ms = time_ms - last_time_ms;
float td = (elapsed_ms) / 1000.0f;
rain_generate(s[generate_index++], screen);
if (generate_index >= 300)
generate_index = 0;
float w = sinf(time_ms / 1000.0f) * 0.05f;
blit::Vec2 gvec = blit::Vec2(0, 9.8 * 5);
blit::Vec2 gravity = gvec * td;
for (auto& p : s) {
if (p.generated) {
p.vel += gravity;
p.pos += p.vel * td;
int floor = -3;
if (p.pos.x > floor_position.x &&
p.pos.x < (floor_position.x + floor_position.w))
floor = -3 - (screen.bounds.h - floor_position.y);
if (p.pos.y >= floor) {
p.pos.y = floor;
float bounce = (GetRandomFloat(10)) / 80.0f;
p.vel.y *= -bounce;
p.vel.x = (GetRandomFloat(30) - 15);
}
p.age++;
int a = p.age / 2;
int r = 100 - (a / 2);
int g = 255 - (a / 2);
int b = 255; // -(a * 4);
if (p.vel.length() > 20) {
screen.pen = blit::Pen(b, g, r, 100);
screen.pixel(p.pos + blit::Point(0, screen.bounds.h - 1));
screen.pen = blit::Pen(b, g, r, 160);
screen.pixel(p.pos + blit::Point(0, screen.bounds.h + 1));
}
screen.pen = blit::Pen(b, g, r, 180);
screen.pixel(p.pos + blit::Point(0, screen.bounds.h + 2));
}
}
last_time_ms = time_ms;
};
// Given a ring buffer full of uint32_t values, return the average value
// or zero if empty (or iteration error).
uint32_t CalcAverageUint32Value(PrefixedEntryRingBuffer& ring_buffer) {
uint64_t sum = 0;
uint32_t count = 0;
for (const auto& entry_info : ring_buffer) {
PW_ASSERT(entry_info.buffer.size() == sizeof(uint32_t));
uint32_t val;
std::memcpy(&val, entry_info.buffer.data(), sizeof(val));
sum += val;
count++;
}
return count == 0 ? 0 : sum / count;
}
} // namespace
void MainTask(void* pvParameters) {
// Timing variables
uint32_t frame_start_millis = pw::spin_delay::Millis();
uint32_t frames = 0;
int frames_per_second = 0;
std::array<wchar_t, 40> fps_buffer = {0};
std::wstring_view fps_view(fps_buffer.data(), 0);
std::byte draw_buffer[30 * sizeof(uint32_t)];
std::byte flush_buffer[30 * sizeof(uint32_t)];
PrefixedEntryRingBuffer draw_times;
PrefixedEntryRingBuffer flush_times;
draw_times.SetBuffer(draw_buffer);
flush_times.SetBuffer(flush_buffer);
pw::board_led::Init();
PW_CHECK_OK(Common::Init());
Display& display = Common::GetDisplay();
Framebuffer framebuffer = display.GetFramebuffer();
PW_ASSERT(framebuffer.is_valid());
blit::Surface screen = blit::Surface(
(uint8_t*)framebuffer.data(),
blit::PixelFormat::RGB565,
blit::Size(framebuffer.size().width, framebuffer.size().height));
screen.pen = blit::Pen(0, 0, 0, 255);
screen.clear();
display.ReleaseFramebuffer(std::move(framebuffer));
uint32_t delta_screen_draw = 0;
// The display loop.
while (1) {
uint32_t start = pw::spin_delay::Millis();
framebuffer = display.GetFramebuffer();
PW_ASSERT(framebuffer.is_valid());
screen.data = (uint8_t*)framebuffer.data();
// Draw Phase
// Clear the screen
screen.pen = blit::Pen(0, 0, 0);
screen.clear();
// Draw 32blit animation
std::string text = "Pigweed + 32blit";
auto text_size = screen.measure_text(text, blit::minimal_font, true);
blit::Rect text_rect(
blit::Point((screen.bounds.w / 2) - (text_size.w / 2),
(screen.bounds.h * .75) - (text_size.h / 2)),
text_size);
rain(screen, start - delta_screen_draw, text_rect);
screen.pen = blit::Pen(0xFF, 0xFF, 0xFF);
screen.text(
text, blit::minimal_font, text_rect, true, blit::TextAlign::top_left);
delta_screen_draw = pw::spin_delay::Millis() - start;
// Update timers
uint32_t end = pw::spin_delay::Millis();
uint32_t time = end - start;
draw_times.PushBack(pw::as_bytes(pw::span{std::addressof(time), 1}));
start = end;
display.ReleaseFramebuffer(std::move(framebuffer));
time = pw::spin_delay::Millis() - start;
flush_times.PushBack(pw::as_bytes(pw::span{std::addressof(time), 1}));
// Every second make a log message.
frames++;
if (pw::spin_delay::Millis() > frame_start_millis + 1000) {
frames_per_second = frames;
frames = 0;
PW_LOG_INFO("FPS:%d, Draw:%dms, Flush:%dms",
frames_per_second,
CalcAverageUint32Value(draw_times),
CalcAverageUint32Value(flush_times));
int len = std::swprintf(fps_buffer.data(),
fps_buffer.size(),
L"FPS:%d, Draw:%dms, Flush:%dms",
frames_per_second,
CalcAverageUint32Value(draw_times),
CalcAverageUint32Value(flush_times));
fps_view = std::wstring_view(fps_buffer.data(), len);
frame_start_millis = pw::spin_delay::Millis();
}
}
}
int main(void) {
#if defined(USE_FREERTOS)
TaskHandle_t task_handle = xTaskCreateStatic(MainTask,
"main",
s_freertos_stack.size(),
/*pvParameters=*/nullptr,
tskIDLE_PRIORITY,
s_freertos_stack.data(),
&s_freertos_tcb);
PW_CHECK_NOTNULL(task_handle); // Ensure it succeeded.
vTaskStartScheduler();
#else
MainTask(/*pvParameters=*/nullptr);
#endif
return 0;
}