Skip to content

[REQUEST] Offscreen rendering inquiry #816

@Kaned1as

Description

@Kaned1as

Please confirm the following points:

  • This question is NOT about the Android apps in the Play Store
  • I have searched the project page to check if the question was already asked elsewhere

Topic

Development and Contributing

Your Request

Hello projectM team! I'm trying to understand what's wrong with my offscreen rendering pipeline. I launch it with the following command:

ffmpeg -i "/media/data/music/test.mp3" -f f32le -acodec pcm_f32le -ar 48000 - \
   | ./projectm-render-pipe \
   | ffmpeg -i "/media/data/music/test.mp3" -f ppm_pipe -r 60 -i - -map 0:a:0 -map 1:v:0 -y rendered.mp4

Here's what I get:
image

Here's what I expect to get:
image

As you can see there's a certain "glow" around the edges. Somehow when I render it offscreen it is not present. I'm kind of a rookie in graphics pipelines but I'm trying to implement offscreen rendering for ProjectM right now and before I submit a PR i need to get it right.

Here's my code that I use for offscreen rendering
#include "projectM-4/core.h"
#include <array>
#include <iostream>
#include <sstream>

#include <EGL/egl.h>
#include <GL/gl.h>
#include <projectM-4/projectM.h>
#include <vector>

static constexpr int kFixedFps = 60;
static constexpr int kAudioSamplerate = 48000;
static constexpr int kPbufferWidth = 1280;
static constexpr int kPbufferHeight = 720;
static constexpr int kBytesPerPixel = 3;

static const std::array configAttribs = {
        EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
        EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
        EGL_BLUE_SIZE, 8,
        EGL_GREEN_SIZE, 8,
        EGL_RED_SIZE, 8,
        EGL_ALPHA_SIZE, 8,
        EGL_DEPTH_SIZE, 8,
        EGL_NONE
};    

static const std::array contextAttribs = {
      EGL_CONTEXT_MAJOR_VERSION, 3,
      EGL_CONTEXT_MINOR_VERSION, 3,
      EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
      EGL_NONE,
};

static const std::array pbufferAttribs = {
      EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGB,
      EGL_TEXTURE_TARGET,  EGL_TEXTURE_2D,
      EGL_WIDTH, kPbufferWidth,
      EGL_HEIGHT, kPbufferHeight,
      EGL_NONE,
};

static auto screenshotToPpm(size_t width, size_t height, unsigned char *pixels) -> std::string {
    std::stringstream output;
    output << "P6 " << width << ' ' << height << ' ' << 255 << '\n';
    output.write((const char *) pixels, kBytesPerPixel * height * width);
    return output.str();
}

static void assertEglError(const std::string& msg) {
  EGLint error = eglGetError();

  if (error != EGL_SUCCESS) {
    std::stringstream exc;
    exc << "EGL error 0x" << std::hex << error << " at " << msg;
    throw std::runtime_error(exc.str());
  }
}


auto main(int argc, char *argv[]) -> int {
  // 1. Initialize EGL
  EGLDisplay eglDpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);

  EGLint major = 0;
  EGLint minor = 0;
  eglInitialize(eglDpy, &major, &minor);
  assertEglError("initializing EGL");

  // 2. Select an appropriate configuration
  EGLint numConfigs = 0;
  EGLConfig eglCfg = nullptr;
  eglChooseConfig(eglDpy, configAttribs.data(), &eglCfg, 1, &numConfigs);
  assertEglError("choosing EGL config");

  // 3. Create a surface
  EGLSurface eglSurf = eglCreatePbufferSurface(eglDpy, eglCfg, pbufferAttribs.data());
  assertEglError("creating EGL surface");

  // 4. Bind the API
  eglBindAPI(EGL_OPENGL_API);
  assertEglError("binding OpenGL API");

  // 5. Create a context and make it current
  EGLContext eglCtx = eglCreateContext(eglDpy, eglCfg, EGL_NO_CONTEXT, contextAttribs.data());
  assertEglError("creating EGL context");

  eglMakeCurrent(eglDpy, eglSurf, eglSurf, eglCtx);
  assertEglError("making EGL context current");

  // from now on use your OpenGL context
  auto *projectm = projectm_create();
  projectm_load_preset_file(projectm, "file:///usr/share/projectM/presets/cream-of-the-crop/Waveform/Wire Circular/$$$ Royal - Mashup (191).milk", false);
  auto textures = std::vector<const char *> {"/usr/share/projectM/textures/cream-of-the-crop/"};
  projectm_set_texture_search_paths(projectm, textures.data(), 1);
  projectm_set_mesh_size(projectm, 64, 48);
  projectm_set_window_size(projectm, kPbufferWidth, kPbufferHeight);
  projectm_set_soft_cut_duration(projectm, 3);
  projectm_set_preset_duration(projectm, 30);
  projectm_set_easter_egg(projectm, 0.0);
  projectm_set_hard_cut_enabled(projectm, false);
  projectm_set_hard_cut_duration(projectm, 60);
  projectm_set_hard_cut_sensitivity(projectm, 1.0);
  projectm_set_beat_sensitivity(projectm, 1.0);
  projectm_set_aspect_correction(projectm, true);
  projectm_set_fps(projectm, kFixedFps);
  projectm_set_fixed_fps(projectm, kFixedFps);

  // buffer size for 60 fps should fit into 16ms
  // for 16000 bitrate that's 266.66 samples for each channel
  constexpr float samplesPerFrame = 2.0 * kAudioSamplerate / kFixedFps;    // 533.33
  constexpr auto bufferLen = static_cast<size_t>(samplesPerFrame) + 1;     // 534
  std::array<float, bufferLen> pcmBuffer{};

  constexpr int size = kBytesPerPixel * kPbufferHeight * kPbufferWidth;
  std::array<unsigned char, size> rgbBuffer{};
  size_t samplesRead = 0;
  for (size_t i = 0; ; i++) {
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    // special arithmetic for cases where samplerate is not fully divisible by fps
    // without this we eventually get un-synced video and audio
    auto nextBatch = static_cast<size_t>(samplesPerFrame * i) - samplesRead;  // could be 533, could be 534
    std::cin.read(reinterpret_cast<char *>(pcmBuffer.data()), nextBatch * sizeof(decltype(pcmBuffer)::value_type));
    if (std::cin.eof()) {
      break;
    }

    samplesRead += nextBatch;
    // std::cerr << "Iteration " << i << " filled buffer size " << samplesToRead << " with data" << '\n';

    auto samplesToSubmit = std::min((unsigned int) bufferLen, projectm_pcm_get_max_samples());
    projectm_pcm_add_float(projectm, pcmBuffer.data(), samplesToSubmit, PROJECTM_STEREO);
    projectm_opengl_render_frame(projectm);

    glReadPixels(0, 0, kPbufferWidth, kPbufferHeight, GL_RGB, GL_UNSIGNED_BYTE, rgbBuffer.data());
    auto ppm = screenshotToPpm(kPbufferWidth, kPbufferHeight, rgbBuffer.data());
    std::cout << ppm;
  }
  

  // 6. Terminate EGL when finished
  eglDestroySurface(eglDpy, eglSurf);
  eglDestroyContext(eglDpy, eglCtx);
  eglTerminate(eglDpy);

  return 0;
}

I can also upload the full video or my small changes to timekeeping if you need it. I haven't touched any of GL-related code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionThis issue is a general question about projectM and the surrounding ecosystem.triageThis is a new issue which hasn't been reviewed yet by a staff member.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions