raspberrypi / userland

Source code for ARM side libraries for interfacing to Raspberry Pi GPU.
BSD 3-Clause "New" or "Revised" License
2.05k stars 1.09k forks source link

Dispmanx API stops working after HDMI resolution change #461

Open juj opened 6 years ago

juj commented 6 years ago

gba_quake2

I've been poking on a project that fits a Raspberry Pi 3 CM inside the shell of a Gameboy Advance (video), running a Waveshare32b SPI based display. In this configuration, there is no display connected to HDMI, but instead I run a custom software driver fbcp-ili9341 that interfaces with the dispmanx API to grab framebuffer display and push the pixel data out to SPI.

One issue that is happening is that it looks like the dispmanx_ APIs stop working if the system changes resolution, by an application, or also when changed via tvservice command line interface. To reproduce, try

test.cpp

#include <stdio.h>
#include <bcm_host.h>

int main()
{
  bcm_host_init();
  DISPMANX_DISPLAY_HANDLE_T display = vc_dispmanx_display_open(0);
  if (!display) { printf("vc_dispmanx_display_open failed!\n"); exit(1); }

  DISPMANX_MODEINFO_T display_info;
  int ret = vc_dispmanx_display_get_info(display, &display_info);
  if (ret) { printf("vc_dispmanx_display_get_info failed!\n"); exit(1); }

  int framebufferSizeBytes = display_info.width * display_info.height * 2;
  uint16_t *videoCoreFramebuffer = (uint16_t *)malloc(framebufferSizeBytes);
  memset(videoCoreFramebuffer, 0, framebufferSizeBytes);

  uint32_t image_prt;
  DISPMANX_RESOURCE_HANDLE_T screen_resource = vc_dispmanx_resource_create(VC_IMAGE_RGB565, display_info.width, display_info.height, &image_prt);
  if (!screen_resource) { printf("vc_dispmanx_resource_create failed!\n"); exit(1); }
  VC_RECT_T rect;
  vc_dispmanx_rect_set(&rect, 0, 0, display_info.width, display_info.height);

  vc_dispmanx_snapshot(display, screen_resource, (DISPMANX_TRANSFORM_T)0);
  vc_dispmanx_resource_read_data(screen_resource, &rect, videoCoreFramebuffer, display_info.width*2);

  uint32_t data = 0;
  for(int i = 0; i < framebufferSizeBytes/2; ++i)
    data += videoCoreFramebuffer[i];
  printf("Data: %u\n", data);
}

and build with g++ test.cpp -o test -I/opt/vc/include -L/opt/vc/lib -lbcm_host.

Running with the following /boot/config.txt for display configuration:

hdmi_force_hotplug=1
hdmi_group=2
hdmi_mode=87
hdmi_cvt=320 240 60 1 0 0 0

and after boot of the Pi, with no HDMI display connected, no applications open but the Pi open in console, the above test code gives

pi@retropie:~ $ tvservice -s
state 0x12000a [HDMI DMT (87) RGB full 4:3 x4], 320x240 @ 59.00Hz, progressive
pi@retropie:~ $ ./test
Data: 3470187170

which is good, the application was able to capture some pixel data.

However, the following scenarios cause the above test code to begin to fail:

Change resolution

pi@retropie:~ $ tvservice -s
state 0x12000a [HDMI DMT (87) RGB full 4:3 x4], 320x240 @ 59.00Hz, progressive
pi@retropie:~ $ ./test
Data: 3470187170 ## Good
pi@retropie:~ $ tvservice --modes=CEA
Group CEA has 2 modes:
           mode 4: 1280x720 @ 60Hz 16:9, clock:74MHz progressive
           mode 16: 1920x1080 @ 60Hz 16:9, clock:148MHz progressive
pi@retropie:~ $ tvservice --explicit="CEA 4 HDMI"
Powering on HDMI with explicit settings (CEA mode 4)
pi@retropie:~ $ tvservice -s
state 0x12000a [HDMI CEA (4) RGB lim 16:9], 1280x720 @ 60.00Hz, progressive
pi@retropie:~ $ ./test
Data: 0 ## Bad

Here after changing the display mode, dispmanx snapshots no longer work but start to return black frames.

Change resolution, but change it back to original

Changing resolution back to "what was working originally" 320x240 does not restore dispmanx to work:

pi@retropie:~ $ tvservice -s
state 0x12000a [HDMI DMT (87) RGB full 4:3 x4], 320x240 @ 59.00Hz, progressive
pi@retropie:~ $ ./test
Data: 3470187170
pi@retropie:~ $ tvservice --explicit="CEA 4 HDMI"
Powering on HDMI with explicit settings (CEA mode 4)
pi@retropie:~ $ tvservice --explicit="DMT 87 HDMI"
Powering on HDMI with explicit settings (DMT mode 87)
pi@retropie:~ $ tvservice -s
state 0x12000a [HDMI DMT (87) RGB full 4:3 x4], 320x240 @ 59.00Hz, progressive
pi@retropie:~ $ ./test
Data: 0

Change resolution to what is already set

It looks like changing the resolution to the same mode that is already currently set causes dispmanx to start outputting black as well:

pi@retropie:~ $ tvservice -s
state 0x12000a [HDMI DMT (87) RGB full 4:3 x4], 320x240 @ 59.00Hz, progressive
pi@retropie:~ $ ./test
Data: 3470187170
pi@retropie:~ $ tvservice --explicit="DMT 87 HDMI"
Powering on HDMI with explicit settings (DMT mode 87)
pi@retropie:~ $ tvservice -s
state 0x12000a [HDMI DMT (87) RGB full 4:3 x4], 320x240 @ 59.00Hz, progressive
pi@retropie:~ $ ./test
Data: 0

Powering HDMI off and then on

pi@retropie:~ $ tvservice -s
state 0x12000a [HDMI DMT (87) RGB full 4:3 x4], 320x240 @ 59.00Hz, progressive
pi@retropie:~ $ ./test
Data: 3470187170
pi@retropie:~ $ tvservice -o
Powering off HDMI
pi@retropie:~ $ tvservice -s
state 0x120002 [TV is off]
pi@retropie:~ $ tvservice --explicit="DMT 87 HDMI"
Powering on HDMI with explicit settings (DMT mode 87)
pi@retropie:~ $ tvservice -s
state 0x12000a [HDMI DMT (87) RGB full 4:3 x4], 320x240 @ 59.00Hz, progressive
pi@retropie:~ $ ./test
vc_dispmanx_display_open failed!

causes dispmanx to no longer initialize.


Are the above scenarios expected to work? Or I wonder if the relationship between tvservice and dispmanx is not as 1:1, but there's some initialization/setup steps missing when changing the display?

Also, what is the role of tvservice versus other methods for changing the HDMI display resolution? Is tvservice the utility through which all applications must go when switching display modes, or are there other utilities? (I wonder if the above test method is flawed for example if tvservice should not be used in this kind of way, or something similar)

Thanks for reading through, any suggestions would be most welcome!

juj commented 6 years ago

Searching the web for related discussions, this link suggests that calling fbset -depth 8 && fbset -depth 16 might have an effect here. Trying this out with

pi@retropie:~ $ tvservice -s
state 0x12000a [HDMI DMT (87) RGB full 4:3 x4], 320x240 @ 59.00Hz, progressive
pi@retropie:~ $ ./test
Data: 1388129224
pi@retropie:~ $ tvservice --explicit="DMT 87 HDMI"
Powering on HDMI with explicit settings (DMT mode 87)
pi@retropie:~ $ tvservice -s
state 0x12000a [HDMI DMT (87) RGB full 4:3 x4], 320x240 @ 59.00Hz, progressive
pi@retropie:~ $ ./test
Data: 0
pi@retropie:~ $ fbset -depth 8 && fbset -depth 16
pi@retropie:~ $ ./test
Data: 0
pi@retropie:~ $ vcgencmd display_power
display_power=1
pi@retropie:~ $ fbset

mode "320x240"
    geometry 320 240 320 240 16
    timings 0 0 0 0 0 0 0
    rgba 5/11,6/5,5/0,0/16
endmode

is no luck unfortunately. Attempting some combos of fbset -xres 320 -yres 240 -vxres 320 -vyres 240 from here and/or vcgencmd display_power 0 followed by vcgencmd display_power 1 from here did not have working effect on this either.

JamesH65 commented 5 years ago

Sorry about delay on this - just going through the backlog and found it.

This is expected behaviour if using tvservice on its own.

When the HDMI mode is changed, dispmanx discards all framebuffers etc to do with the mode as sizes etc will have changed. This results in a blank screen, both on HDMI but also, I believe, any snapshots.

To get dispmanx to reinitilaise all the required buffers, and get HDMI displaying again, you need to run fbset with the appropriate parameters. fbset ensures that the firmware creates a new framebuffer and attaches it to dispmanx. One proviso, fbset is a no op if the thing you are setting to is the same as what it already thinks is there, so you may need to fbset <something else>. then go back to your original framebuffer size (e.g. -depth 16 then -depth 24).

As an example, check out the way omxplayer does this - see the script that runs it for details, which omxplayer will get its location.

jamesc2151 commented 10 months ago

Just got a my TFT working on the newest copy of buster .. I have a 320x240 SPI Serial ILI9341 from amazon and compiled using the command .. `` cmake -DSPI_BUS_CLOCK_DIVISOR=30 -DSTATISTICS=0 -DGPIO_TFT_DATA_CONTROL=24 -DGPIO_TFT_RESET_PIN=20 -DGPIO_TFT_BACKLIGHT=23 -DBACKLIGHT_CONTROL=ON -DDISPLAY_BREAK_ASPECT_RATIO_WHEN_SCALING=ON -DILI9341=ON -DDISPLAY_ROTATE_180_DEGREES=ON ..

It works like a charm so for those f you with this screen it works my config text has the fake dtoverlay=vc4-fkms-v3d for hdmi and dtparam=spi=off framebuffer_width=1024 framebuffer_height=768 hdmi_group=2 hdmi_mode=1 hdmi_mode=87 hdmi_cvt 640 480 60 6 0 0 0 display_rotate=90 avoid_warnings=1

Took me about 10 minutes after I found a wiring setup that it liked as I have other hardware installed 2 OLE displays and a RTC that default wiring kept interfering with.

6by9 commented 10 months ago

Buster is deprecated, as is DispmanX.

There is a TinyDRM driver for ILI9341 panels, and an overlay on https://github.com/6by9/linux/tree/rpi-6.1.y-tinydrm that will configure and load it (dtoverlay=tinydrm,waveshare24). Wayfire will then happily extend the desktop onto that display, or use any application that talks to a DRM backend with it.

jamesc2151 commented 10 months ago

Sorry didn't mean buster... I meant to type bookworm... I use my system headless and watch this little console for Cava to display my sound bar graphs