Serenity Operating System
at portability 262 lines 7.9 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27/* Fire.cpp - a (classic) graphics demo for Serenity, by pd. 28 * heavily based on the Fabien Sanglard's article: 29 * http://fabiensanglard.net/doom_fire_psx/index.html 30 * 31 * Future directions: 32 * [X] This does suggest the need for a palletized graphics surface. Thanks kling! 33 * [X] alternate column updates, or vertical interlacing. this would certainly alter 34 * the effect, but the update load would be halved. 35 * [/] scaled blit 36 * [ ] dithering? 37 * [X] inlining rand() 38 * [/] precalculating and recycling random data 39 * [ ] rework/expand palette 40 * [ ] switch to use tsc values for perf check 41 * [ ] handle mouse events differently for smoother painting (queue) 42 * [ ] handle fire bitmap edges better 43*/ 44 45#include <LibCore/ElapsedTimer.h> 46#include <LibGUI/Application.h> 47#include <LibGUI/Label.h> 48#include <LibGUI/Painter.h> 49#include <LibGUI/Widget.h> 50#include <LibGUI/Window.h> 51#include <LibGfx/Bitmap.h> 52#include <stdio.h> 53#include <stdlib.h> 54#include <time.h> 55 56#define FIRE_WIDTH 320 57#define FIRE_HEIGHT 168 58#define FIRE_MAX 29 59 60static const Color s_palette[] = { 61 Color(0x07, 0x07, 0x07), Color(0x1F, 0x07, 0x07), Color(0x2F, 0x0F, 0x07), 62 Color(0x47, 0x0F, 0x07), Color(0x57, 0x17, 0x07), Color(0x67, 0x1F, 0x07), 63 Color(0x77, 0x1F, 0x07), Color(0x9F, 0x2F, 0x07), Color(0xAF, 0x3F, 0x07), 64 Color(0xBF, 0x47, 0x07), Color(0xC7, 0x47, 0x07), Color(0xDF, 0x4F, 0x07), 65 Color(0xDF, 0x57, 0x07), Color(0xD7, 0x5F, 0x07), Color(0xD7, 0x5F, 0x07), 66 Color(0xD7, 0x67, 0x0F), Color(0xCF, 0x6F, 0x0F), Color(0xCF, 0x7F, 0x0F), 67 Color(0xCF, 0x87, 0x17), Color(0xC7, 0x87, 0x17), Color(0xC7, 0x8F, 0x17), 68 Color(0xC7, 0x97, 0x1F), Color(0xBF, 0x9F, 0x1F), Color(0xBF, 0xA7, 0x27), 69 Color(0xBF, 0xAF, 0x2F), Color(0xB7, 0xAF, 0x2F), Color(0xB7, 0xB7, 0x37), 70 Color(0xCF, 0xCF, 0x6F), Color(0xEF, 0xEF, 0xC7), Color(0xFF, 0xFF, 0xFF) 71}; 72 73/* Random functions... 74 * These are from musl libc's prng/rand.c 75*/ 76static uint64_t seed; 77 78void my_srand(unsigned s) 79{ 80 seed = s - 1; 81} 82 83static int my_rand(void) 84{ 85 seed = 6364136223846793005ULL * seed + 1; 86 return seed >> 33; 87} 88 89/* 90 * Fire Widget 91*/ 92class Fire : public GUI::Widget { 93 C_OBJECT(Fire) 94public: 95 virtual ~Fire() override; 96 void set_stat_label(RefPtr<GUI::Label> l) { stats = l; }; 97 98private: 99 Fire(); 100 RefPtr<Gfx::Bitmap> bitmap; 101 RefPtr<GUI::Label> stats; 102 103 virtual void paint_event(GUI::PaintEvent&) override; 104 virtual void timer_event(Core::TimerEvent&) override; 105 virtual void mousedown_event(GUI::MouseEvent& event) override; 106 virtual void mousemove_event(GUI::MouseEvent& event) override; 107 virtual void mouseup_event(GUI::MouseEvent& event) override; 108 109 bool dragging; 110 int timeAvg; 111 int cycles; 112 int phase; 113}; 114 115Fire::Fire() 116{ 117 bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::Indexed8, { 320, 200 }); 118 119 /* Initialize fire palette */ 120 for (int i = 0; i < 30; i++) 121 bitmap->set_palette_color(i, s_palette[i]); 122 123 /* Set remaining entries to white */ 124 for (int i = 30; i < 256; i++) 125 bitmap->set_palette_color(i, Color::White); 126 127 dragging = false; 128 timeAvg = 0; 129 cycles = 0; 130 phase = 0; 131 132 my_srand(time(nullptr)); 133 stop_timer(); 134 start_timer(20); 135 136 /* Draw fire "source" on bottom row of pixels */ 137 for (int i = 0; i < FIRE_WIDTH; i++) 138 bitmap->bits(bitmap->height() - 1)[i] = FIRE_MAX; 139 140 /* Set off initital paint event */ 141 //update(); 142} 143 144Fire::~Fire() 145{ 146} 147 148void Fire::paint_event(GUI::PaintEvent& event) 149{ 150 Core::ElapsedTimer timer; 151 timer.start(); 152 153 GUI::Painter painter(*this); 154 painter.add_clip_rect(event.rect()); 155 156 /* Blit it! */ 157 painter.draw_scaled_bitmap(event.rect(), *bitmap, bitmap->rect()); 158 159 timeAvg += timer.elapsed(); 160 cycles++; 161} 162 163void Fire::timer_event(Core::TimerEvent&) 164{ 165 /* Update only even or odd columns per frame... */ 166 phase++; 167 if (phase > 1) 168 phase = 0; 169 170 /* Paint our palettized buffer to screen */ 171 for (int px = 0 + phase; px < FIRE_WIDTH; px += 2) { 172 for (int py = 1; py < 200; py++) { 173 int rnd = my_rand() % 3; 174 175 /* Calculate new pixel value, don't go below 0 */ 176 u8 nv = bitmap->bits(py)[px]; 177 if (nv > 0) 178 nv -= (rnd & 1); 179 180 /* ...sigh... */ 181 int epx = px + (1 - rnd); 182 if (epx < 0) 183 epx = 0; 184 else if (epx > FIRE_WIDTH) 185 epx = FIRE_WIDTH; 186 187 bitmap->bits(py - 1)[epx] = nv; 188 } 189 } 190 191 if ((cycles % 50) == 0) { 192 dbgprintf("%d total cycles. finished 50 in %d ms, avg %d ms\n", cycles, timeAvg, timeAvg / 50); 193 stats->set_text(String::format("%d ms", timeAvg / 50)); 194 timeAvg = 0; 195 } 196 197 update(); 198} 199 200/* 201 * Mouse handling events 202*/ 203void Fire::mousedown_event(GUI::MouseEvent& event) 204{ 205 if (event.button() == GUI::MouseButton::Left) 206 dragging = true; 207 208 return GUI::Widget::mousedown_event(event); 209} 210 211/* FIXME: needs to account for the size of the window rect */ 212void Fire::mousemove_event(GUI::MouseEvent& event) 213{ 214 if (dragging) { 215 if (event.y() >= 2 && event.y() < 398 && event.x() <= 638) { 216 int ypos = event.y() / 2; 217 int xpos = event.x() / 2; 218 bitmap->bits(ypos - 1)[xpos] = FIRE_MAX + 5; 219 bitmap->bits(ypos - 1)[xpos + 1] = FIRE_MAX + 5; 220 bitmap->bits(ypos)[xpos] = FIRE_MAX + 5; 221 bitmap->bits(ypos)[xpos + 1] = FIRE_MAX + 5; 222 } 223 } 224 225 return GUI::Widget::mousemove_event(event); 226} 227 228void Fire::mouseup_event(GUI::MouseEvent& event) 229{ 230 if (event.button() == GUI::MouseButton::Left) 231 dragging = false; 232 233 return GUI::Widget::mouseup_event(event); 234} 235 236/* 237 * Main 238*/ 239int main(int argc, char** argv) 240{ 241 GUI::Application app(argc, argv); 242 243 auto window = GUI::Window::construct(); 244 window->set_double_buffering_enabled(false); 245 window->set_title("Fire"); 246 window->set_resizable(false); 247 window->set_rect(100, 100, 640, 400); 248 249 auto fire = Fire::construct(); 250 window->set_main_widget(fire); 251 252 auto time = fire->add<GUI::Label>(); 253 time->set_relative_rect({ 0, 4, 40, 10 }); 254 time->move_by({ window->width() - time->width(), 0 }); 255 time->set_foreground_color(Color::from_rgb(0x444444)); 256 fire->set_stat_label(time); 257 258 window->show(); 259 window->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-demo.png")); 260 261 return app.exec(); 262}