Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.x] Window transparency support on Android #51935

Merged
merged 1 commit into from
Sep 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions doc/classes/OS.xml
Original file line number Diff line number Diff line change
Expand Up @@ -970,10 +970,10 @@
If [code]true[/code], the window is minimized.
</member>
<member name="window_per_pixel_transparency_enabled" type="bool" setter="set_window_per_pixel_transparency_enabled" getter="get_window_per_pixel_transparency_enabled" default="false">
If [code]true[/code], the window background is transparent and window frame is removed.
If [code]true[/code], the window background is transparent and the window frame is removed.
Use [code]get_tree().get_root().set_transparent_background(true)[/code] to disable main viewport background rendering.
[b]Note:[/b] This property has no effect if [b]Project &gt; Project Settings &gt; Display &gt; Window &gt; Per-pixel transparency &gt; Allowed[/b] setting is disabled.
[b]Note:[/b] This property is implemented on HTML5, Linux, macOS and Windows.
[b]Note:[/b] This property has no effect if [member ProjectSettings.display/window/per_pixel_transparency/allowed] setting is disabled.
[b]Note:[/b] This property is implemented on HTML5, Linux, macOS, Windows, and Android. It can't be changed at runtime for Android. Use [member ProjectSettings.display/window/per_pixel_transparency/enabled] to set it at startup instead.
</member>
<member name="window_position" type="Vector2" setter="set_window_position" getter="get_window_position" default="Vector2( 0, 0 )">
The window position relative to the screen, the origin is the top left corner, +Y axis goes to the bottom and +X axis goes to the right.
Expand Down
6 changes: 5 additions & 1 deletion doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -447,10 +447,14 @@
If [code]true[/code], the home indicator is hidden automatically. This only affects iOS devices without a physical home button.
</member>
<member name="display/window/per_pixel_transparency/allowed" type="bool" setter="" getter="" default="false">
If [code]true[/code], allows per-pixel transparency in a desktop window. This affects performance, so leave it on [code]false[/code] unless you need it.
If [code]true[/code], allows per-pixel transparency for the window background. This affects performance, so leave it on [code]false[/code] unless you need it.
See [member OS.window_per_pixel_transparency_enabled] for more details.
[b]Note:[/b] This feature is implemented on HTML5, Linux, macOS, Windows, and Android.
</member>
<member name="display/window/per_pixel_transparency/enabled" type="bool" setter="" getter="" default="false">
Sets the window background to transparent when it starts.
See [member OS.window_per_pixel_transparency_enabled] for more details.
[b]Note:[/b] This feature is implemented on HTML5, Linux, macOS, Windows, and Android.
</member>
<member name="display/window/size/always_on_top" type="bool" setter="" getter="" default="false">
Forces the main window to be always on top.
Expand Down
5 changes: 5 additions & 0 deletions platform/android/export/export_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2552,6 +2552,11 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportP
command_line_strings.push_back("--debug_opengl");
}

bool translucent = ProjectSettings::get_singleton()->get("display/window/per_pixel_transparency/enabled");
if (translucent) {
command_line_strings.push_back("--translucent");
}

if (command_line_strings.size()) {
r_command_line_flags.resize(4);
encode_uint32(command_line_strings.size(), &r_command_line_flags.write[0]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
private boolean use_32_bits = false;
private boolean use_immersive = false;
private boolean use_debug_opengl = false;
private boolean translucent = false;
private boolean mStatePaused;
private boolean activityResumed;
private int mState;
Expand Down Expand Up @@ -357,7 +358,7 @@ private void onVideoInit() {
// ...add to FrameLayout
containerLayout.addView(edittext);

mView = new GodotView(activity, this, xrMode, use_gl3, use_32_bits, use_debug_opengl);
mView = new GodotView(activity, this, xrMode, use_gl3, use_32_bits, use_debug_opengl, translucent);
containerLayout.addView(mView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
edittext.setView(mView);
io.setEdit(edittext);
Expand Down Expand Up @@ -608,6 +609,8 @@ public void onCreate(Bundle icicle) {
use_32_bits = true;
} else if (command_line[i].equals("--debug_opengl")) {
use_debug_opengl = true;
} else if (command_line[i].equals("--translucent")) {
translucent = true;
} else if (command_line[i].equals("--use_immersive")) {
use_immersive = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // check if the application runs on an android 4.4+
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public class GodotView extends GLSurfaceView {
private final GodotRenderer godotRenderer;

public GodotView(Context context, Godot godot, XRMode xrMode, boolean p_use_gl3,
boolean p_use_32_bits, boolean p_use_debug_opengl) {
boolean p_use_32_bits, boolean p_use_debug_opengl, boolean p_translucent) {
super(context);
GLUtils.use_gl3 = p_use_gl3;
GLUtils.use_32 = p_use_32_bits;
Expand All @@ -86,7 +86,8 @@ public GodotView(Context context, Godot godot, XRMode xrMode, boolean p_use_gl3,
this.inputHandler = new GodotInputHandler(this);
this.detector = new GestureDetector(context, new GodotGestureHandler(this));
this.godotRenderer = new GodotRenderer();
init(xrMode, false, 16, 0);

init(xrMode, p_translucent, 16, 0);
}

public void initInputDevices() {
Expand Down Expand Up @@ -139,6 +140,7 @@ private void init(XRMode xrMode, boolean translucent, int depth, int stencil) {
* is interpreted as any 32-bit surface with alpha by SurfaceFlinger.
*/
if (translucent) {
this.setZOrderOnTop(true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pouleyKetchoupp Can you clarify what this does? According to the documentation:

By setting this, you cause it to be placed above the window. This means that none of the contents of the window this SurfaceView is in will be visible on top of its surface.

Which would mean that it would prevent additional component from rendering on top of the surface; is that the desired behavior?

Could you also walk me through how per-pixel transparency is achieved; it may help me understand better the logic in the PR.
Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which would mean that it would prevent additional component from rendering on top of the surface; is that the desired behavior?

Yes, this allows Godot rendering to be displayed on top, so a plugin can render behind the transparent surface, rather than always in front (like it does by default). As a side effect, plugins can't render on top of the surface when this is enabled, as you mentioned, but it's the only way I've found to achieve transparent rendering.

Could you also walk me through how per-pixel transparency is achieved; it may help me understand better the logic in the PR.

Transparent rendering is achieved this way:

  1. per_pixel_transparency/allowed & per_pixel_transparency/enabled are both set to true in Project Settings
  2. get_tree().get_root().transparent_bg = true; is called at runtime to make the viewport transparent
  3. At Android export time, --translucent is added to the command line (this step is needed on 3.x because Main::setup() is called after the surface is initialized, it won't be needed on master).
  4. At Android runtime, --translucent command line is checked to create a surface with transparent capabilities (on master it will just check the project settings directly)
  5. When the plugin is initialized, its own rendering frame is placed on top of the main frame by default, so this can be used to move it behind:
ViewGroup parentView = (ViewGroup)pluginFrame.getParent();
parentView.removeView(pluginFrame);
parentView.addView(pluginFrame, 0);

(from what I've seen, both this and setZOrderOnTop(true) are needed for transparency to work correctly)

The result is the plugin does its custom rendering (for example camera preview) and Godot draws on top of it with transparent background (for example to add UI elements).

Let me know if there's more I can clarify!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember that transparent viewports aren't supported yet with the Vulkan renderer: #40651

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pouleyKetchoupp Sorry for the delay, I was out for the past week.

Regarding ordering of the surface and of the plugins' views, we control that logic here and here, so couldn't just have a flag that tells us if translucent is enabled, and changed the order of the views as shown in your sample code?
With that approach, would we still need to set the setZOrderOnTop(...) flag?

Also, when you say the surface is transparent, does that mean that any view(s) behind it will be visible? I'm trying to clarify here since making the background transparent in Godot may not mean the same as making the surface view background transparent (which would allow to see underlying views).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries!

Regarding ordering of the surface and of the plugins' views, we control that logic here and here, so couldn't just have a flag that tells us if translucent is enabled, and changed the order of the views as shown in your sample code?
With that approach, would we still need to set the setZOrderOnTop(...) flag?

I'm not sure why (it could be the way GL surfaces work), but both are needed for the system to work correctly.
I've tested again since last time and here's how it goes:
-setZOrderOnTop is needed for transparent rendering, without it the Godot surface renders with black background and any view behind it is not visible.
-Changing the order of views is not strictly needed if setZOrderOnTop is set, but it helps with inputs as it allows Godot inputs to be taken over views placed behind.

Also, when you say the surface is transparent, does that mean that any view(s) behind it will be visible? I'm trying to clarify here since making the background transparent in Godot may not mean the same as making the surface view background transparent (which would allow to see underlying views).

Yes, it allows other views to be visible behind it.

Using this feature, you can also make a transparent app and see the device desktop in the background. But that requires to also change the style of the main Godot app to this:

<style name="GodotAppMainTheme" parent="@android:style/Theme.Black.NoTitleBar.Fullscreen">
     <item name="android:windowIsTranslucent">true</item>
     <item name="android:windowBackground">@android:color/transparent</item>
</style>

So far I haven't found a nice way to enable it in the export process or by code, but it would be a nice addition to this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About setting the transparent theme in the app:
From what I've just tested, changing the theme to a new one with windowIsTranslucent attribute even in the app onCreate function is not enough to make it work. It can modify the color but not the translucent flag.

I guess the best way to handle it would be to change the theme in the manifest file during export, but do you know how to handle it with custom builds? It looks like the export process fixes the manifest file only for standard builds.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pouleyKetchoupp Do you have an example for the logic you use to update the theme?

In general, the theme needs to be set before even calling setContentView(...).
So the logic should look something like:

public void onCreate(Bundle savedInstanceState) {
		if (hasTranslucentFlag())
                    setTheme(R.style.GodotAppTranslucentTheme);
                else
                    setTheme(R.style.GodotAppMainTheme);
		super.onCreate(savedInstanceState);
}

where GodotAppTranslucentTheme would be as you described above:

<style name="GodotAppTranslucentTheme" parent="@android:style/Theme.Black.NoTitleBar.Fullscreen">
     <item name="android:windowIsTranslucent">true</item>
     <item name="android:windowBackground">@android:color/transparent</item>
</style>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on your example, even just setting the transparent theme without checking any flag like this:

public void onCreate(Bundle savedInstanceState) {
                setTheme(R.style.GodotAppTranslucentTheme);
		super.onCreate(savedInstanceState);
}

doesn't seem to work, unless windowIsTranslucent is set to true in the default theme as well.

It looks like this flag can't be changed once the app is being created, and it works by adding it in the default theme only because the splash theme inherits from the default theme (unless I'm missing something).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this flag can't be changed once the app is being created, and it works by adding it in the default theme only because the splash theme inherits from the default theme (unless I'm missing something).

@pouleyKetchoupp I thought you were still looking into this. If not, I'm good with approving and unblocking this PR, and attempting to update the app theme for translucency in another PR. For that approach, can you create an issue that documents what you already tried; I can then take a look and see if anything is missing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the confusion! Yes let me finalize this PR and open an issue to solve the app theme thing separately.

this.getHolder().setFormat(PixelFormat.TRANSLUCENT);
}

Expand Down
2 changes: 2 additions & 0 deletions platform/android/os_android.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ Error OS_Android::initialize(const VideoMode &p_desired, int p_video_driver, int
}
}

transparency_enabled = p_desired.layered;

if (gl_initialization_error) {
OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.\n"
"Please try updating your Android version.",
Expand Down
5 changes: 5 additions & 0 deletions platform/android/os_android.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ class OS_Android : public OS_Unix {

int video_driver_index;

bool transparency_enabled = false;

public:
// functions used by main to initialize/deinitialize the OS
virtual int get_video_driver_count() const;
Expand Down Expand Up @@ -150,6 +152,9 @@ class OS_Android : public OS_Unix {
virtual String get_model_name() const;
virtual int get_screen_dpi(int p_screen = 0) const;

virtual bool get_window_per_pixel_transparency_enabled() const { return transparency_enabled; }
virtual void set_window_per_pixel_transparency_enabled(bool p_enabled) { ERR_FAIL_MSG("Setting per-pixel transparency is not supported at runtime, please set it in project settings instead."); }

virtual String get_unique_id() const;

virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const;
Expand Down