hajimehoshi / ebiten

Ebitengine - A dead simple 2D game engine for Go
https://ebitengine.org
Apache License 2.0
10.87k stars 651 forks source link

an option to specify color space for macOS and iOS #2871

Closed mjbartholomew closed 1 month ago

mjbartholomew commented 9 months ago

Ebitengine Version

2.6.3

Operating System

Go Version (go version)

go1.21.5 darwin/amd64

What steps will reproduce the problem?

Explicitly define the screen color via ebiten.Image.Fill()

func (g *Game) Draw(screen *ebiten.Image) {
    screen.Fill(color.RGBA{0x3b, 0x90, 0x30, 0xff})
}

As can be seen in the image under the "What happens instead?" section, it also impacts colors defined by images.

What is the expected result?

The expected result would be that the colors would appear the same in the app's window as they do in other applications (like Chrome, Safari, Slate, Preview)

What happens instead?

The colors are rendered differently. Using the Digital Color Meter on macOS, I can see that the color #3b9030 renders as expected in Safari, Chrome, Preview, and Slate, yet when displayed in an Ebitengine application window, I instead see #009219.

The top-half of the following image is from an app created with Ebitengine, whereas the bottom half is from the app Slate. As you can see, the colors are off when drawn by Ebitengine. Every channel (Red, Green, and Blue) have been altered, with the Red channel changing from 0x3b in the code to 0x00 on screen.

Screenshot 2023-12-23 at 3 50 58 PM

The brown block colors are also off, but just not as pronounced as can be seen with the green background color.

Anything else you feel useful to add?

No response

hajimehoshi commented 9 months ago

This is a difference of color space. Ebitengine uses kCGColorSpaceDisplayP3 and this might be different from other applications.

The expected result would be that the colors would appear the same in the app's window as they do in other applications (like Chrome, Safari, Slate, Preview)

What about Firefox?

mjbartholomew commented 9 months ago

Firefox is the same as Chrome and Safari. Here is a screenshot of the browsers in the following order: Firefox, Chrome, and Safari.

Screenshot 2023-12-23 at 11 36 03 PM

The content of the test file is:

<!DOCTYPE html>
<html>
    <head>
        <style>
            body {
                color: red;
                background-color: #3b9030;
            }
        </style>
    </head>
    <body></body>
</html>

All 3 of the browsers display the same color for #3b9030. Consulting the Digital Color Meter app, when I hover over the pixels in each of the browser windows, it displays the decimal equivalent for #3b9030.

Digital Color Meter on Firefox

I wondered if colorspace played a part in this issue. I believe the browsers interpret CSS colors, as well as untagged images, as sRGB. Firefox made changes in version 89 to follow this approach.

hajimehoshi commented 9 months ago

OK so the differences are color spaces, and now all the modern browsers now adopt sRGB by default.

I don't think this is a bug in Ebitengine. Ebitengine is not responsible to output colors that are compatible with other applications.

Also, with OpenGL mode, there is no way to control color spaces and probably Display P3 is used. Ebitengine tries to emulate this with Metal for consistency. You can try it with EBITENGINE_GRAPHICS_LIBRARY=opengl go run path/to/your/game.

mjbartholomew commented 9 months ago

Using EBITENGINE_GRAPHICS_LIBRARY=opengl go run path/to/your/game made a difference, and the green color now renders correctly. Is there a way to bake this environment value into the binary? When I try EBITENGINE_GRAPHICS_LIBRARY=opengl go build the resulting binary still produces the unexpected colors.

Or is it possible to add an option in Ebitengine to use the sRGB color profile for macOS (e.g. MTKView.colorPixelFormat = .bgra8Unorm_srgb)?

mjbartholomew commented 9 months ago

For those researching how to change the graphics library, you need to use ebiten.RunGameWithOptions() instead of ebiten.RunGame()

ebiten.RunGameWithOptions() has a second parameter, which takes a variable of type RunGameOptions. The following is an example of how to create that variable (in this case, named op), and make the change to the GraphicsLibrary value to select OpenGL.

op := &ebiten.RunGameOptions{}
op.GraphicsLibrary = ebiten.GraphicsLibraryOpenGL

Note: I have found that using GraphicsLibraryOpenGL on macOS does result in a less smooth experience, with frames dropping, when compared to using Metal.

hajimehoshi commented 9 months ago

Using EBITENGINE_GRAPHICS_LIBRARY=opengl go run path/to/your/game made a difference, and the green color now renders correctly.

That's odd. I got the same result with OpenGL and Metal. I'm using MacBook Pro 2020.

image

Also, the Metal version is not incorrect. This just uses a different color space than sRGB and some colors might not be able to be represented in sRGB.

hajimehoshi commented 9 months ago

Or is it possible to add an option in Ebitengine to use the sRGB color profile for macOS (e.g. MTKView.colorPixelFormat = .bgra8Unorm_srgb)?

I can consider this if you want, but as I said, this is not controllable when OpenGL is used.

mjbartholomew commented 9 months ago

Or is it possible to add an option in Ebitengine to use the sRGB color profile for macOS (e.g. MTKView.colorPixelFormat = .bgra8Unorm_srgb)?

I can consider this if you want, but as I said, this is not controllable when OpenGL is used.

As OpenGL has been deprecated by Apple in favor of Metal, I would greatly appreciate it if you would consider adding that as an option.

mjbartholomew commented 9 months ago

Using EBITENGINE_GRAPHICS_LIBRARY=opengl go run path/to/your/game made a difference, and the green color now renders correctly.

That's odd. I got the same result with OpenGL and Metal. I'm using MacBook Pro 2020.

Here are the results I have on my 2018 Mac mini (EBITENGINE_GRAPHICS_LIBRARY=opengl on the right window):

Screenshot 2023-12-24 at 2 40 38 AM
hajimehoshi commented 6 months ago

Metal

Use CAMetalLayer's colorSpace property. Possible values are:

DirectX

Use DXGI_OUTPUT_DESC1's ColorSpace member or IDXGISwapChain3::SetColorSpace1. Possible values are:

How can we specify Display P3 for DirectX?

OpenGL

There is no standard way to specify a color space?

WebGL

WebGLRenderingContext's drawingBufferColorSpace is available. This feature is experimental.

hajimehoshi commented 6 months ago

We need more investigation.

hajimehoshi commented 1 month ago

I failed to find a good solution for DirectX. Probably just ignoring the option would be fine.

hajimehoshi commented 1 month ago

image

Here is the result of an experimental option 'ColorSpace'. (sRGB vs DisplayP3)