Directly reading FrameBuffer bytearray yields garbled results.

All ESP32 boards running MicroPython.
Target audience: MicroPython users with an ESP32 board.
Post Reply
RSC Games
Posts: 4
Joined: Wed May 04, 2022 2:18 pm

Directly reading FrameBuffer bytearray yields garbled results.

Post by RSC Games » Wed May 04, 2022 6:47 pm

I have been writing a c module to render a plane with a perspective transform (like Super Mario Kart on SNES). I wrote a module in Python to do it, but performance was horrible (2.5 FPS). So I booted up my Pi and started writing a module. Initially I faced a lot of exceptions, but eventually resolved them (thanks dpgeorge!). Now, the module works and compiles, and it returns pixel data, but not in the format that I expected. I looked at https://github.com/micropython/micropyt ... framebuf.c and found the code that manipulates the buffers, but when I used the code for myself (on MHLSB images), I just get garbage.

Here is the relevant code:

Code: Select all

// My Python Mode 7 renderer reimplemented in C++. It changes the plane in place, so no return is required.
uint8_t* render_m7(uint8_t* img, uint16_t w, uint16_t h, float x, float y, float rotation)
{
  // Store the cosine and sine of the angle for increased render speeds, as a pointer dereference is way faster than a floating point calculation.
  float rot_cos = cos(rot);
  float rot_sin = sin(rot);

  // In MicroPython, fast locals were used in place of globals because local lookup speed was 
  // faster. In C++, globals are just allocated on heap, which isn't incredibly fast, but is 
  // way faster than even local lookups in Python.

  // A list of bit masks was declared here, but for C++ I'll use a switch statement for masking.
  // TODO: Perform bit masking later on.

  // Finally, we create the renderer viewport.
  // ...more important in the software Python renderer. Not needed here, as this step was previously done.
  uint32_t img_sz = w * h;

  // Rendering time has begun. Create an int to keep track of the y iteration through the resulting image.
  uint16_t iy = 0;

  // Store the FOV. This is only set once.
  uint32_t py = fov;

  // Iterate through every single scanline on the screen.
  for (int32_t y = 0; y < render_height + YRES_DIV_2; y++)
  {
    
    // ix is reset every scanline.
    uint16_t ix = 0;

    // Only set this once per y iteration because its value does not change per x iteration.
    int32_t pz = y + horizon;

    // Prevent Guru Meditation Error: Core 1 panic'ed! (IntegerDivideByZero)
    if (pz == 0)
    {
      pz = 1;
    }

    float sy = py / pz;

    // Precalculate the multiplication for the rotation vectors to save a few CPU cycles (and allow them to be used by the audio renderer).
    float sy_sin = (sy * rot_sin);
    float sy_cos = (sy * rot_cos);

    // Iterate over every pixel in the scanline.
    int32_t neg_xres = (XRES_DIV_2 * -1);
    for (int32_t x = neg_xres; x <= XRES_DIV_2; x++)
    {
      int32_t px = x;

      // Other half of the projection system.
      float sx = px / pz;

      // Optimized the trig out of this, now just does multiplication.
      float sxp = (sx * rot_cos) - sy_sin;
      float syp = (sx * rot_sin) + sy_cos;

      int32_t imgx = sxp * scaling + g_x;
      int32_t imgy = syp * scaling + g_y;
      

      // Get a specific bit position and access it.
      // o_byte_val is the current byte that the pixel resides in.
      // o_bit_val is the bit that corresponds to the pixel.
      //int32_t img_loc = (((imgx + 1) * (imgy + 1)) - 1);
      //int16_t wnd_loc = (((ix + 1) * (iy + 1)) - 1);
      //int32_t o_byte_val = img_loc / 8;
      //int8_t o_bit_val = img_loc % 8;
      //int32_t n_byte_val = wnd_loc / 8;
      //int8_t n_bit_val = wnd_loc % 8;
      size_t o_index = (imgx + imgy) >> 3;
      uint32_t o_offset = 7 - (imgx & 0x07);
      size_t n_index = (ix * iy) >> 3;
      uint16_t n_offset = 7 - (ix & 0x07);

      // px_val stores the fetched pixel value.
      uint8_t px_val = 0;

      // Now we access the pixel, and XOR it into the byte, after bit shifting it to make life easier.
      // Since this is hard even for me to read, I'm going to explain it here (first in pseudocode-like language, then in English).
      // pixel_value = ( preimage[orignal_byte_value] & (1 << original_bit_value)) >> original_bit_value;
      // What this does is grab a single pixel value from an array. It will look up the byte, and then use a bit mask to grab one bit, and then right bit shift it down so that it is now a bool.
      // rendered_plane[new_byte_value] |= (pixel_value << new_bit_value)
      // This sets a bit at the targeted plane byte (via the use of a unary XOR operator), and left bit shifts the bool up to the actual pixel location.
      if ((imgx * imgy) - 1 < img_sz) {
          //printf("img_pos %i img x %i img y %i \n", img_loc, imgx, imgy);
          //printf("IMG_BYTE_VAL %i BYTE_VAL %i BIT_VAL %i \n", img[o_byte_val], o_byte_val, o_bit_val);
          //px_val = (img[o_byte_val] & (0b1 << (0x8 - o_bit_val))) >> o_bit_val;
          px_val = (img[o_index] >> (o_offset)) & 0x01;
          //printf("px %i \n", px_val);
          //wndw_ptr[n_byte_val] |= (px_val << (0x8 - n_bit_val));
          //printf("wnd_pos %i ix %i iy %i \n", wnd_loc, ix, iy);
          //printf("WND_BYTE_VAL %i BYTE_VAL %i BIT_VAL %i \n", wndw_ptr[n_byte_val], n_byte_val, n_bit_val);
      }
      else {
          px_val = 0;
      }
      wndw_ptr[n_index] = (wndw_ptr[n_index] & ~(0x01 << n_offset)) | (px_val << n_offset);

      // Increment the image x location.
      ix++;
    }

    // Increment the image y location.
    iy++;
  }

  // No need to return anything, as the image is in a higher scope and is edited by a pointer.
  return wndw_ptr;
}

RSC Games
Posts: 4
Joined: Wed May 04, 2022 2:18 pm

Re: Directly reading FrameBuffer bytearray yields garbled results.

Post by RSC Games » Sat Jun 18, 2022 1:05 pm

I think this can be marked as solved. I decided to reimplement this with the framebuffer class in C.

Post Reply