Merge branch 'master' into pep-657-munge

This commit is contained in:
Tom Rothamel
2025-02-14 00:07:08 -05:00
committed by GitHub
21 changed files with 197 additions and 102 deletions
-1
View File
@@ -255,7 +255,6 @@ name_blacklist = {
"renpy.gl2.assimp.loader",
"renpy.gl2.assimp.loader_lock",
"renpy.gl2.gl2draw.default_position",
"renpy.lexer._python_updatecache",
}
+1 -1
View File
@@ -1,5 +1,5 @@
# Copyright 2004-2024 Tom Rothamel <pytom@bishoujo.us>
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
+1
View File
@@ -326,6 +326,7 @@ init -1100 python:
if version <= (8, 3, 99):
config.old_show_expression = True
config.mipmap = True
# The version of Ren'Py this script is intended for, or
+44 -44
View File
@@ -1,4 +1,4 @@
/* Copyright 2004-2024 Tom Rothamel <pytom@bishoujo.us>
/* Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
@@ -26,12 +26,12 @@ const DEBUG_OUT = false;
const USE_FRAME_CB = 'requestVideoFrameCallback' in HTMLVideoElement.prototype;
renpyAudio = { };
renpyAudio = {};
/**
* A map from channel to channel object.
*/
let channels = { };
let channels = {};
let next_chan_id = 0;
let context = new AudioContext();
@@ -50,14 +50,14 @@ let get_channel = (channel) => {
}
c = {
playing : null,
queued : null,
stereo_pan : context.createStereoPanner(),
fade_volume : context.createGain(),
primary_volume : context.createGain(),
secondary_volume : context.createGain(),
relative_volume : context.createGain(),
paused : false,
playing: null,
queued: null,
stereo_pan: context.createStereoPanner(),
fade_volume: context.createGain(),
primary_volume: context.createGain(),
secondary_volume: context.createGain(),
relative_volume: context.createGain(),
paused: false,
video: false,
video_el: null,
chan_id: next_chan_id++,
@@ -278,7 +278,7 @@ let video_start = (c) => {
c.video_el.muted = true;
c.video_el.play().then(() => {
// TODO?
}).catch( (e) => {
}).catch((e) => {
console.warn('Video is NOT playing even when muted', e.name);
renpyAudio.videoPlayPrompt(renpyAudio._web_video_prompt, c.video_el);
});
@@ -386,24 +386,24 @@ renpyAudio.queue = (channel, file, name, synchro_start, fadein, tight, start, en
}
const q = {
url: url,
name : name,
start : start, // TODO?
end : end, // TODO?
relative_volume : relative_volume,
started : null,
fadein : fadein, // TODO?
fadeout: null, // TODO?
tight : tight, // TODO?
filter : renpyAudio.getFilter(afid),
synchro_start : synchro_start,
url: url,
name: name,
start: start, // TODO?
end: end, // TODO?
relative_volume: relative_volume,
started: null,
fadein: fadein, // TODO?
fadeout: null, // TODO?
tight: tight, // TODO?
filter: renpyAudio.getFilter(afid),
synchro_start: synchro_start,
period_stats: [0, 0], // time sum, count
fetch_stats: [0, 0],
draw_stats: [0, 0, 0, 0], // time sum, count, first timestamp, last timestamp
blob_stats: [0, 0],
array_stats: [0, 0],
file_stats: [0, 0, 0, 0], // time sum, count, first timestamp, last timestamp
period_stats: [0, 0], // time sum, count
fetch_stats: [0, 0],
draw_stats: [0, 0, 0, 0], // time sum, count, first timestamp, last timestamp
blob_stats: [0, 0],
array_stats: [0, 0],
file_stats: [0, 0, 0, 0], // time sum, count, first timestamp, last timestamp
};
if (c.video_el === null) {
@@ -413,11 +413,11 @@ renpyAudio.queue = (channel, file, name, synchro_start, fadein, tight, start, en
c.video_el.playsInline = true; // For autoplay on Safari
document.body.appendChild(c.video_el);
c.video_el.addEventListener('loadedmetadata', function() {
c.video_el.addEventListener('loadedmetadata', function () {
c.video_size = [c.video_el.videoWidth, c.video_el.videoHeight];
});
c.video_el.addEventListener('error', function(e) {
c.video_el.addEventListener('error', function (e) {
});
@@ -433,7 +433,7 @@ renpyAudio.queue = (channel, file, name, synchro_start, fadein, tight, start, en
c.is_playing = false;
});
c.video_el.addEventListener('playing', function() {
c.video_el.addEventListener('playing', function () {
c.is_playing = true;
});
@@ -462,19 +462,19 @@ renpyAudio.queue = (channel, file, name, synchro_start, fadein, tight, start, en
}
const q = {
source : null,
buffer : null,
name : name,
start : start,
end : end,
relative_volume : relative_volume,
started : null,
fadein : fadein,
source: null,
buffer: null,
name: name,
start: start,
end: end,
relative_volume: relative_volume,
started: null,
fadein: fadein,
fadeout: null,
tight : tight,
tight: tight,
file: file,
filter : renpyAudio.getFilter(afid),
synchro_start : synchro_start,
filter: renpyAudio.getFilter(afid),
synchro_start: synchro_start,
};
function reuseBuffer(c) {
@@ -868,7 +868,7 @@ renpyAudio.read_video = (channel, video_tex, width, height) => {
if (DEBUG_OUT) {
// DEBUG Dumps all method calls to renpyAudio
renpyAudio._nodump = {'queue_depth': 1, 'playing_name': 1, 'video_ready': 1, 'read_video': 1};
renpyAudio._nodump = { 'queue_depth': 1, 'playing_name': 1, 'video_ready': 1, 'read_video': 1 };
renpyAudio = new Proxy(renpyAudio, {
get(target, prop) {
const origMethod = target[prop];
+19 -19
View File
@@ -1,4 +1,4 @@
/* Copyright 2004-2024 Tom Rothamel <pytom@bishoujo.us>
/* Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
@@ -20,7 +20,7 @@
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
let afidToFilter = {0 : null};
let afidToFilter = { 0: null };
/**
* Given an afid, return the filter associated with it. Returns null if the
@@ -94,7 +94,7 @@ renpyAudio.disconnectFilter = function (filter, source, destination) {
}
renpyAudio.filter = { }
renpyAudio.filter = {}
let filter = renpyAudio.filter;
/**
@@ -112,7 +112,7 @@ filter.filterToFilter = function (filter1, filter2) {
/**
* Connects a filter to a node.
*/
filter.filterToNode = function(filter, node) {
filter.filterToNode = function (filter, node) {
for (let output of filter.outputs) {
output.connect(node);
}
@@ -121,22 +121,22 @@ filter.filterToNode = function(filter, node) {
/**
* Connects a node to a filter.
*/
filter.nodeToFilter = function(node, filter) {
filter.nodeToFilter = function (node, filter) {
for (let input of filter.inputs) {
node.connect(input);
}
}
filter.Null = function() {
filter.Null = function () {
let node = new GainNode(renpyAudio.context, { gain: 1 });
return {
inputs: [ node ],
outputs: [ node ],
inputs: [node],
outputs: [node],
};
}
filter.Crossfade = function(afid1, afid2, t) {
filter.Crossfade = function (afid1, afid2, t) {
let filter1 = renpyAudio.getFilter(afid1);
let filter2 = renpyAudio.getFilter(afid2);
@@ -150,13 +150,13 @@ filter.Crossfade = function(afid1, afid2, t) {
gain2.gain.linearRampToValueAtTime(1, renpyAudio.context.currentTime + t);
return {
inputs: [ ...filter1.inputs, ...filter2.inputs ],
inputs: [...filter1.inputs, ...filter2.inputs],
outputs: [gain1, gain2],
};
};
filter.Biquad = function(kind, frequency, Q, gain) {
filter.Biquad = function (kind, frequency, Q, gain) {
let node = new BiquadFilterNode(renpyAudio.context, {
type: kind,
frequency: frequency,
@@ -170,7 +170,7 @@ filter.Biquad = function(kind, frequency, Q, gain) {
};
};
filter.Sequence = function(...filters) {
filter.Sequence = function (...filters) {
let first = filters[0];
let last = filters[filters.length - 1];
@@ -184,7 +184,7 @@ filter.Sequence = function(...filters) {
};
}
filter.Mix = function(...filters) {
filter.Mix = function (...filters) {
let inputs = [];
let outputs = [];
@@ -199,7 +199,7 @@ filter.Mix = function(...filters) {
};
}
filter.Multiply = function(factor) {
filter.Multiply = function (factor) {
let node = new GainNode(renpyAudio.context, { gain: factor });
return {
@@ -209,7 +209,7 @@ filter.Multiply = function(factor) {
}
filter.Delay = function(delay) {
filter.Delay = function (delay) {
if (typeof delay !== "number") {
delay = delay[0];
@@ -223,7 +223,7 @@ filter.Delay = function(delay) {
};
}
filter.Comb = function(delay, child, multiplier, wet) {
filter.Comb = function (delay, child, multiplier, wet) {
if (typeof delay !== "number") {
delay = delay[0];
}
@@ -237,8 +237,8 @@ filter.Comb = function(delay, child, multiplier, wet) {
multiplierNode.connect(delayNode);
let rv = {
inputs: [ delayNode ],
outputs: [ multiplierNode ],
inputs: [delayNode],
outputs: [multiplierNode],
};
if (wet) {
@@ -252,7 +252,7 @@ filter.Comb = function(delay, child, multiplier, wet) {
}
filter.WetDry = function(child, wet, dry) {
filter.WetDry = function (child, wet, dry) {
let wetNode = new GainNode(renpyAudio.context, { gain: wet });
let dryNode = new GainNode(renpyAudio.context, { gain: dry });
+3
View File
@@ -1133,6 +1133,9 @@ controller_blocklist = [
"030000006d0400000000", # Razer Xbox 360 Controller (#4622)
]
# Should other textures be mipmapped by default?
mipmap = "auto"
# Should dissolve transitions be mipmapped by default?
mipmap_dissolves = False
+10 -4
View File
@@ -355,7 +355,7 @@ class Cache(object):
texsurf = ce.surf.subsurface(ce.bounds)
renpy.display.render.mutated_surface(texsurf)
ce.texture = renpy.display.draw.load_texture(texsurf)
ce.texture = renpy.display.draw.load_texture(texsurf, properties={ 'mipmap' : image.mipmap })
# This was loaded while predicting images for immediate use,
# so get it onto the GPU.
@@ -586,7 +586,7 @@ class ImageBase(renpy.display.displayable.Displayable):
obsolete = True
obsolete_list = [ ]
mipmap = True
# If the image failed to load, a placeholder used to report the error.
fail = None
@@ -601,6 +601,7 @@ class ImageBase(renpy.display.displayable.Displayable):
self.cache = properties.pop('cache', True)
self.optimize_bounds = properties.pop('optimize_bounds', True)
self.oversample = properties.pop('oversample', 1)
self.mipmap = properties.pop('mipmap', "auto")
if self.oversample <= 0:
raise Exception("Image's oversample parameter must be greater than 0.")
@@ -2012,7 +2013,7 @@ def image(arg, loose=False, **properties):
"""
:doc: im_image
:name: Image
:args: (filename, *, optimize_bounds=True, oversample=1, dpi=96, **properties)
:args: (filename, *, optimize_bounds=True, oversample=1, dpi=96, mipmap="auto", **properties)
Loads an image from a file. `filename` is a
string giving the name of the file.
@@ -2036,6 +2037,11 @@ def image(arg, loose=False, **properties):
The DPI of an SVG image. This defaults to 96, but that can be
increased to render the SVG larger, and decreased to render
it smaller.
`mipmap`
If this is "auto", then mipmaps are generated for the image only if the game is scaled down to less than
75% of its default size. If this is True, mipmaps are always generated. If this is False, mipmaps are never
generated.
"""
"""
@@ -2160,7 +2166,7 @@ def unoptimized_texture(d):
"""
if isinstance(d, ImageBase):
return UnoptimizedTexture(d)
return UnoptimizedTexture(d, mipmap=True)
else:
return d
+2
View File
@@ -58,6 +58,8 @@ cdef class GL2Draw:
cdef public Matrix virt_to_draw
cdef public Matrix draw_to_virt
cdef public bint auto_mipmap
# The matrix that goes from drawable space to the window. This isn't used
# directly, it's used to determine if something is being drawn in a wa
# that it should be lined up with pixels.
+5
View File
@@ -137,6 +137,9 @@ cdef class GL2Draw:
# The old value of fullscreen.
self.old_fullscreen = False
# Should mipmaps be generated when mipmap == "auto"?
self.auto_mipmap = False
def get_texture_size(self):
"""
Returns the amount of memory locked up in textures.
@@ -611,6 +614,8 @@ cdef class GL2Draw:
self.init_fbo()
self.texture_loader.init()
self.auto_mipmap = self.draw_per_virt < 0.75
def resize(self):
"""
Documented in renderer.
+1 -1
View File
@@ -1,4 +1,4 @@
# Copyright 2004-2024 Tom Rothamel <pytom@bishoujo.us>
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
+1 -1
View File
@@ -1,4 +1,4 @@
# Copyright 2004-2024 Tom Rothamel <pytom@bishoujo.us>
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
+1 -1
View File
@@ -1,4 +1,4 @@
# Copyright 2004-2024 Tom Rothamel <pytom@bishoujo.us>
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
+1 -1
View File
@@ -1,4 +1,4 @@
# Copyright 2004-2024 Tom Rothamel <pytom@bishoujo.us>
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
+1 -1
View File
@@ -1,4 +1,4 @@
# Copyright 2004-2024 Tom Rothamel <pytom@bishoujo.us>
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
+1 -1
View File
@@ -1,4 +1,4 @@
# Copyright 2004-2024 Tom Rothamel <pytom@bishoujo.us>
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
+10 -5
View File
@@ -307,7 +307,12 @@ cdef class GLTexture(GL2Model):
when it's loaded).
"""
return self.properties.get("mipmap", True)
rv = self.properties.get("mipmap", renpy.config.mipmap)
if rv == "auto":
rv = renpy.display.draw.auto_mipmap
return rv
def get_number(GLTexture self):
return self.number if renpy.emscripten else None
@@ -333,7 +338,7 @@ cdef class GLTexture(GL2Model):
"""
self.properties = {
"mipmap" : properties.get("mipmap", True),
"mipmap" : properties.get("mipmap", renpy.config.mipmap),
"pixel_perfect" : properties.get("pixel_perfect", False),
}
@@ -585,7 +590,7 @@ cdef class GLTexture(GL2Model):
max_level = renpy.config.max_mipmap_level
if not properties.get("mipmap", True):
if not self.has_mipmaps():
max_level = 0
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, max_level)
@@ -630,7 +635,7 @@ cdef class GLTexture(GL2Model):
cdef GLuint level = renpy.config.max_mipmap_level
if not properties.get("mipmap", True):
if not self.has_mipmaps():
level = 0
glBindTexture(GL_TEXTURE_2D, tex)
@@ -654,7 +659,7 @@ cdef class GLTexture(GL2Model):
self.loader.total_texture_size -= int(self.width * self.height * 4 * 1.34)
else:
self.loader.total_texture_size -= int(self.width * self.height * 4)
except TypeError:
except (TypeError, AttributeError):
pass # Let's not error on shutdown.
def load(self):
+39 -20
View File
@@ -1165,18 +1165,32 @@ def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=
else:
py_mode = mode
flags |= new_compile_flags
try:
with save_warnings():
tree = compile(source, filename, py_mode, ast.PyCF_ONLY_AST | flags, True)
except SyntaxError as orig_e:
tree: Any = None
with save_warnings():
try:
fixed_source = renpy.compat.fixes.fix_tokens(source)
with save_warnings():
tree = compile(fixed_source, filename, py_mode, ast.PyCF_ONLY_AST | flags, True)
except Exception:
raise orig_e
tree = compile(
source,
filename,
py_mode,
ast.PyCF_ONLY_AST | flags,
True)
except SyntaxError:
handled = False
try:
fixed_source = renpy.compat.fixes.fix_tokens(source)
tree = compile(
fixed_source,
filename,
py_mode,
ast.PyCF_ONLY_AST | flags,
True)
handled = True
except Exception:
pass
if not handled:
raise
tree = wrap_node.visit(tree)
@@ -1193,18 +1207,23 @@ def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=
if ast_node:
return tree.body
try:
with save_warnings():
rv = compile(tree, filename, py_mode, flags, True)
except SyntaxError as orig_e:
rv: Any = None
with save_warnings():
try:
tree = renpy.compat.fixes.fix_ast(tree)
fix_locations(tree, 1, 0)
with save_warnings():
rv = compile(tree, filename, py_mode, flags, True)
except SyntaxError:
handled = False
try:
tree = renpy.compat.fixes.fix_ast(tree)
fix_locations(tree, 1, 0)
rv = compile(tree, filename, py_mode, flags, True)
except Exception:
raise orig_e
handled = True
except Exception:
pass
if not handled:
raise
if cache:
py_compile_cache[key] = rv
+1 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python
# Copyright 2004-2024 Tom Rothamel <pytom@bishoujo.us>
# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
+20
View File
@@ -47,6 +47,21 @@ merged with the player's script, a library can be placed under game/libs, and wi
`.rpe` and `.rpe.py` files are also searched in the libs directory.
Optional Mipmaps
----------------
Mipmaps are smaller versions of an image that are used when Ren'Py scales an image down. Using mipmaps
prevents the image from becoming jagged when scaled down, but generating mipmaps takes time and can cause the game
to use more memory.
Ren'Py now leaves the decision of if to create mipmaps to the developer, who knows if the game will scale down an
image. To always enable mipmaps, set :var:`config.mipmap` to True. If this isn't set to true, Ren'Py will only
create mipmaps if the display is scaled down to less than 75% of the virtual window size.
Mipmaps will automatically be created for images loaded for the purpose of Live2D or AssimpModel, as these are
likely to be scaled down. Mipmaps can be created for specific images by providing True to the mipmap parameter
of :func:`Image`.
Features
--------
@@ -84,9 +99,14 @@ apply :ref:`text interpolation <text-interpolation>` to the result. Interpolatio
that the function is called from. The triple underscore function also marks the string contained
inside for translation.
Other Changes
-------------
By default, Ren'Py now only creates mipmaps for textures if the display is scaled down to less than .75 of virtual
window size. This is suitable for games that do not scale down images. To enable mipmapping again, set
:var:`config.mipmap` to True.
Ren'Py no longer triggers and autoreload when a file that had not existed comes into existence. This behavior
had been inconsistent, working in some places but not others, required Ren'Py to spent time scanning for files
that do not exist.
+21 -1
View File
@@ -74,7 +74,7 @@ Auto-Forward Mode
.. var:: config.afm_callback = None
If not None, a Python function that is called to determine if it
is safe to auto-forward. If None, an internal function is used to
is safe to auto-forward. If None, an internal function is used to
disable auto-forwarding when a voice is playing, unless :var:`preferences.wait_voice`
is set to False.
@@ -755,10 +755,30 @@ Media (Music, Sound, and Video)
A list of channels that are stopped when entering or returning to the
main menu.
.. var:: config.mipmap = "auto"
This controls if Ren'Py generates mipmaps for image. If True, mipmaps are always generated. If "auto", mipmaps
are generated only if the window is smaller than 75% of the virtual screen size. If False, mipmaps are never
generated.
.. var:: config.mipmap_dissolves = False
If True, mipmaps are generated for dissolve transitions.
This takes the same values as :var:`config.mipmap`.
.. var:: config.mipmap_movies = False
The default value of the mipmap argument to :func:`Movie`.
This takes the same values as :var:`config.mipmap`.
.. var:: config.mipmap_text = False
If True, mipmaps are generated for text.
This takes the same values as :var:`config.mipmap`.
.. var:: config.movie_mixer = "music"
The mixer that is used when a :func:`Movie` automatically defines
+15
View File
@@ -17,6 +17,21 @@ such changes only take effect when the GUI is regenerated.
8.4.0
-----
**Mipmaps**
Mipmaps are smaller versions of an image that are used when Ren'Py scales an image down. Using mipmaps
prevents the image from becoming jagged when scaled down, but generating mipmaps takes time and can cause the game
to use more memory.
Ren'Py now leaves the decision of if to create mipmaps to the developer, who knows if the game will scale down an
image. To always enable mipmaps, set :var:`config.mipmap` to True. If this isn't set to true, Ren'Py will only
create mipmaps if the display is scaled down to less than 75% of the virtual window size.
Mipmaps will automatically be created for images loaded for the purpose of Live2D or AssimpModel, as these are
likely to be scaled down. Mipmaps can be created for specific images by providing True to the mipmap parameter
of :func:`Image`.
**Show expression.** The ``show expression`` statement has been changed so that::
show expression "bg washington"