-
Notifications
You must be signed in to change notification settings - Fork 105
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
The Dark (K)Night #884
The Dark (K)Night #884
Conversation
Not convinced this'll be worth it in Lua, but it'll certainly be in C, especially on x86_64...
We want a final pointer of the native type, but a byte-sized initial data pointer, as pitch is in bytes...
Easily vectorizable by GCC
blitter Unroll those loops! ;)
avoid ghosting. Fix koreader/koreader/1375
Pinging @arooni as a potential PW4 tester ;). Technically, I don't even need you to test this, a strace of what the default Reader does (both in UI & reader) when in nightmode would be great/better :). The PW4 strace build is here, as the one bundled with USBNet will probably crash on mxcfb ioctls on those devices ;). I'll need the output of |
The frontlight level is still annoying with flashing updates, but this probably requires lower-level shenanigans than we have access to to do right, as far as timing is concerned. Might be able to piggy-back on the native API on the PW4/KOA2, though, but this might be non-trivial. |
Bonus point: If the device can do HW inversion, nightmode now works with the C blitter on eInk ;). |
Otherwise we inherit the FULL flag even when it's not needed. Sidebar: Amazon's GL16_INV has a non-stupid flashing behavior. Kudos.
Mwahahaha, even on earlier devices (PW2+), as long as you're running FW >= 5.6.x, GL16_INV has a non-stupid behavior when flashing: it only flashes white once, instead of the weird double-hiccup you get on Kobo. |
Vague attempt at getting rid of the weird flashes on Kobo: diff --git a/ffi/framebuffer_mxcfb.lua b/ffi/framebuffer_mxcfb.lua
index 6e2897a..85290da 100644
--- a/ffi/framebuffer_mxcfb.lua
+++ b/ffi/framebuffer_mxcfb.lua
@@ -249,15 +249,41 @@ local function mxc_update(fb, update_ioctl, refarea, refresh_type, waveform_mode
-- Handle night mode shenanigans
if fb.night_mode then
- -- We're in nightmode! If the device can do HW inversion safely, do that!
- if fb.device:canHWInvert() then
- refarea[0].flags = bor(refarea[0].flags, C.EPDC_FLAG_ENABLE_INVERSION)
- end
+ if fb.device:isKindle() then
+ -- We're in nightmode! If the device can do HW inversion safely, do that!
+ if fb.device:canHWInvert() then
+ refarea[0].flags = bor(refarea[0].flags, C.EPDC_FLAG_ENABLE_INVERSION)
+ end
- -- If the waveform is not fast or GC16, enforce a nightmode-specific mode (usually, GC16), to limit ghosting
- if not fb:_isFastWaveFormMode(waveform_mode) and waveform_mode ~= C.WAVEFORM_MODE_GC16 then
- waveform_mode = fb:_getNightWaveFormMode()
- refarea[0].waveform_mode = waveform_mode
+ -- If the waveform is not fast or GC16, enforce a nightmode-specific mode (usually, GC16), to limit ghosting
+ if not fb:_isFastWaveFormMode(waveform_mode) and waveform_mode ~= C.WAVEFORM_MODE_GC16 then
+ waveform_mode = fb:_getNightWaveFormMode()
+ refarea[0].waveform_mode = waveform_mode
+ end
+ else
+ -- On Kobo, there's no shiny nightmode-specific waveform mode. Which means flashes are awful.
+ -- So, if the device can do HW inversion, fake a flash by splitting a refresh in two:
+ -- a first one, non-inverted, which will essentially flash to white,
+ -- and then, the actual inverted one, which we mùake non-flashing.
+ if fb.device:canHWInvert() then
+ if refresh_type == C.UPDATE_MODE_FULL then
+ -- So, first one, uninverted, non-flashing
+ refarea[0].update_mode = C.UPDATE_MODE_PARTIAL
+ C.ioctl(fb.fd, update_ioctl, refarea)
+ end
+
+ -- Then, the real night-modded one, still partial
+ refarea[0].flags = bor(refarea[0].flags, C.EPDC_FLAG_ENABLE_INVERSION)
+ if not fb:_isFastWaveFormMode(waveform_mode) and waveform_mode ~= C.WAVEFORM_MODE_GC16 then
+ waveform_mode = fb:_getNightWaveFormMode()
+ refarea[0].waveform_mode = waveform_mode
+ end
+ else
+ if not fb:_isFastWaveFormMode(waveform_mode) and waveform_mode ~= C.WAVEFORM_MODE_GC16 then
+ waveform_mode = fb:_getNightWaveFormMode()
+ refarea[0].waveform_mode = waveform_mode
+ end
+ end
end
end It's.... different. You don't get a weird double white-flash, but you get to see the page uninverted on screen for a noticeable delay. Ultimately... not a fan. (Of either behavior, really, but, eh xD). |
On the upside: ghosting is bearable on a Mk.7, while it's fairly ridiculous on my H2O ;). |
Okay, done testing on my end. Stuff todo before it's ready for merge:
|
Also, not actually a scanline fill :D.
Another attempt at playing with those crappy flashes... this time by enforcing a specific waveform mode in the first univerted one. diff --git a/ffi/framebuffer_mxcfb.lua b/ffi/framebuffer_mxcfb.lua
index 6e2897a..4c1018e 100644
--- a/ffi/framebuffer_mxcfb.lua
+++ b/ffi/framebuffer_mxcfb.lua
@@ -249,15 +249,47 @@ local function mxc_update(fb, update_ioctl, refarea, refresh_type, waveform_mode
-- Handle night mode shenanigans
if fb.night_mode then
- -- We're in nightmode! If the device can do HW inversion safely, do that!
- if fb.device:canHWInvert() then
- refarea[0].flags = bor(refarea[0].flags, C.EPDC_FLAG_ENABLE_INVERSION)
- end
+ if fb.device:isKindle() then
+ -- We're in nightmode! If the device can do HW inversion safely, do that!
+ if fb.device:canHWInvert() then
+ refarea[0].flags = bor(refarea[0].flags, C.EPDC_FLAG_ENABLE_INVERSION)
+ end
- -- If the waveform is not fast or GC16, enforce a nightmode-specific mode (usually, GC16), to limit ghosting
- if not fb:_isFastWaveFormMode(waveform_mode) and waveform_mode ~= C.WAVEFORM_MODE_GC16 then
- waveform_mode = fb:_getNightWaveFormMode()
- refarea[0].waveform_mode = waveform_mode
+ -- If the waveform is not fast or GC16, enforce a nightmode-specific mode to limit ghosting & flashing
+ if not fb:_isFastWaveFormMode(waveform_mode) and waveform_mode ~= C.WAVEFORM_MODE_GC16 then
+ waveform_mode = fb:_getNightWaveFormMode()
+ refarea[0].waveform_mode = waveform_mode
+ end
+ else
+ -- On Kobo, there's no shiny nightmode-specific waveform mode. Which means flashes are awful.
+ -- So, if the device can do HW inversion, fake a flash by splitting a refresh in two:
+ -- a first one, non-inverted, REAGL, which will emulate a (much) slower flash to white,
+ -- and then, the actual inverted one, which we make non-flashing.
+ if fb.device:canHWInvert() then
+ if refresh_type == C.UPDATE_MODE_FULL then
+ -- So, first one, uninverted, non-flashing, REAGL
+ refarea[0].update_mode = C.UPDATE_MODE_PARTIAL
+ refarea[0].waveform_mode = C.WAVEFORM_MODE_REAGL
+ -- NOTE: When trying this with A2, don't forget to enforce FORCE_MONOCHROME...
+ C.ioctl(fb.fd, update_ioctl, refarea)
+ -- Next marker, and reset to original waveform mode
+ marker = fb:_get_next_marker()
+ refarea[0].update_marker = marker
+ refarea[0].waveform_mode = waveform_mode or C.WAVEFORM_MODE_GC16
+ end
+
+ -- Then, the real night-modded one, still partial
+ refarea[0].flags = bor(refarea[0].flags, C.EPDC_FLAG_ENABLE_INVERSION)
+ if not fb:_isFastWaveFormMode(waveform_mode) and waveform_mode ~= C.WAVEFORM_MODE_GC16 then
+ waveform_mode = fb:_getNightWaveFormMode()
+ refarea[0].waveform_mode = waveform_mode
+ end
+ else
+ if not fb:_isFastWaveFormMode(waveform_mode) and waveform_mode ~= C.WAVEFORM_MODE_GC16 then
+ waveform_mode = fb:_getNightWaveFormMode()
+ refarea[0].waveform_mode = waveform_mode
+ end
+ end
end
end Here, REAGL, because it's the only one that doesn't flash the previous screen content (... most of the time). I initially started w/ A2, because that appeared to look fine in theory on a Kindle Touch, where I was confirming that GL16_INV safely falls back to AUTO (it does, and that suffers from the same double hiccup flashes), but that turned out to look way, way worse on the Forma ;). Anyway, that still sucks. Just learned another fun quirk, though: flashing a REAGL (in this context), flashes to pure white instead of the previous's screen content. |
And another crappy test, shutting down the FL during flashes... diff --git a/ffi/framebuffer_mxcfb.lua b/ffi/framebuffer_mxcfb.lua
index 6e2897a..b8d5271 100644
--- a/ffi/framebuffer_mxcfb.lua
+++ b/ffi/framebuffer_mxcfb.lua
@@ -249,6 +249,10 @@ local function mxc_update(fb, update_ioctl, refarea, refresh_type, waveform_mode
-- Handle night mode shenanigans
if fb.night_mode then
+ -- If it's a flashing update, turn off the light...
+ if refresh_type == C.UPDATE_MODE_FULL then
+ fb.device:getPowerDevice():toggleFrontlight()
+ end
-- We're in nightmode! If the device can do HW inversion safely, do that!
if fb.device:canHWInvert() then
refarea[0].flags = bor(refarea[0].flags, C.EPDC_FLAG_ENABLE_INVERSION)
@@ -294,6 +298,11 @@ local function mxc_update(fb, update_ioctl, refarea, refresh_type, waveform_mode
--]]
fb.debug("refresh: wait for completion of marker", marker, "with collision_test", collision_test)
fb.mech_wait_update_complete(fb, marker, collision_test)
+
+ if fb.night_mode then
+ -- Lights back on!
+ fb.device:getPowerDevice():toggleFrontlight()
+ end
end
end Not necessarily a fan either, but, it's not completely terrible. |
Except we can't really do a ramp-up/down, because we can't do parallel execution in Lua. Otherwise, something as simple as diff --git a/frontend/device/generic/powerd.lua b/frontend/device/generic/powerd.lua
index 04c0dcad..87974b5f 100644
--- a/frontend/device/generic/powerd.lua
+++ b/frontend/device/generic/powerd.lua
@@ -47,8 +47,24 @@ function BasePowerD:setDissmisBatteryStatus(status) self.battery_warning = statu
function BasePowerD:isChargingHW() return false end
function BasePowerD:frontlightIntensityHW() return 0 end
function BasePowerD:isFrontlightOnHW() return self.fl_intensity > self.fl_min end
-function BasePowerD:turnOffFrontlightHW() self:_setIntensity(self.fl_min) end
-function BasePowerD:turnOnFrontlightHW() self:_setIntensity(self.fl_intensity) end
+function BasePowerD:turnOffFrontlightHW()
+ local util = require("ffi/util")
+ for i = 1,20 do
+ self:_setIntensity(self.fl_intensity - ((self.fl_intensity / 20) * i))
+ if (i < 20) then
+ util.usleep(25)
+ end
+ end
+end
+function BasePowerD:turnOnFrontlightHW()
+ local util = require("ffi/util")
+ for i = 1,20 do
+ self:_setIntensity(self.fl_min + ((self.fl_intensity / 20) * i))
+ if (i < 20) then
+ util.usleep(25)
+ end
+ end
+end
-- Anything needs to be done before do a real hardware suspend. Such as turn off
-- front light.
function BasePowerD:beforeSuspend() end Looks fairly swanky... Except we need it to be non-blocking, which it cleary isn't and can't be ;). |
And that concludes today's experiments ;). Also, according to the kernel headers, GCK16 ought to be the right nightmode waveform on KOA2/PW4, so this should be good to go ;). |
In Lua not, but in KOReader we can :) Lines 222 to 283 in ed04819
(whether it's worth forking for that, and if the forked subprocess can use the frontlight fd, is left as a nightly exercise for the reader :) |
Why, yes, yes it does ;p. I don't even need to sleep, the overhead is high enough that it's not necessary. |
diff --git a/frontend/device/generic/powerd.lua b/frontend/device/generic/powerd.lua
index 04c0dcad..95875e19 100644
--- a/frontend/device/generic/powerd.lua
+++ b/frontend/device/generic/powerd.lua
@@ -47,8 +47,33 @@ function BasePowerD:setDissmisBatteryStatus(status) self.battery_warning = statu
function BasePowerD:isChargingHW() return false end
function BasePowerD:frontlightIntensityHW() return 0 end
function BasePowerD:isFrontlightOnHW() return self.fl_intensity > self.fl_min end
-function BasePowerD:turnOffFrontlightHW() self:_setIntensity(self.fl_min) end
-function BasePowerD:turnOnFrontlightHW() self:_setIntensity(self.fl_intensity) end
+function BasePowerD:turnOffFrontlightHW()
+ local util = require("ffi/util")
+ util.runInSubProcess(function()
+ for i = 1,5 do
+ self:_setIntensity(self.fl_intensity - ((self.fl_intensity / 5) * i))
+ -- NOTE: No need to sleep, the overhead of the syscalls is good enough ;).
+ --[[
+ if (i < 20) then
+ util.usleep(1)
+ end
+ --]]
+ end
+ end, false)
+end
+function BasePowerD:turnOnFrontlightHW()
+ local util = require("ffi/util")
+ util.runInSubProcess(function()
+ for i = 1,5 do
+ self:_setIntensity(self.fl_min + ((self.fl_intensity / 5) * i))
+ --[[
+ if (i < 20) then
+ util.usleep(1)
+ end
+ --]]
+ end
+ end, false)
+end
-- Anything needs to be done before do a real hardware suspend. Such as turn off
-- front light.
function BasePowerD:beforeSuspend() end This works okay w/ the NM hack above, for instance. For a general purpose toggle, I prefer a slower, smoother 20 step ramp, though. (The timing/sleep/overhead thing might be device-specific (i.e., sysfs vs. ioctl vs. lipc)). |
Personally I don't mind the straight on/off. |
There are a couple ways to avoid having to reap zombies ourselves (a double fork so it gets reparented to init straight away, as init will wait on zombies, or SIGCHLD signal shenanigans). |
(Just for info, in case you feel like going at that: don't implement that as the default in the current |
to reap it themselves. Because we wouldn't want that to leave zombies ;)
Managed to fit it in there with an extra optional arg, without that being too convoluted (I think ;p). |
Went with the double-fork method, because I'm familmiar with it from KFMon, and because signals are hell. |
Did not appear to break the thumbnail generation code, which seems to be the main other user ;). |
ffi/util.lua
Outdated
@@ -272,9 +285,18 @@ function util.runInSubProcess(func, with_pipe) | |||
os.exit(0) | |||
end | |||
-- parent/main process | |||
if pid == -1 then -- on failure, fork() returns -1 | |||
if opid < 0 then -- on failure, fork() returns -1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We never get the inner pid in the main process right?
So, why opid
here? Except for clarity. But it's a bit confusing (reading it for the first time) that you use opid
in a non double_fork if
- and that at the end, we return pid
. May be use just one of opid
or pid
everywhere after that, and mention it has no meaning when double_fork ?
Otherwise, looks fine and not too convoluted enough :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless I'm having C hangups, I can't declare a local variable inside a branch, that'd limit it to that branch's scope. So I can't call it pid if !double_fork and opid if double_fork ;).
So I have to set opid in the function scope, which means it gets to be used even when !double_fork, in which case opid == pid ;).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And I do have to make the distinction between inner and outer fork in that final check, it has to be the outer fork fork() failure, as the inner one is handled in the double_fork branch ;).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, wait, I get what you mean. -_-".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really following each case you mention, because it feels I don't have to, if the following is right:
you could just use the single local variable pid defined outside the branch and forks.
Then in each branch, you're a different forked process: each forked process can use the single local pid the way it wants too: it won't be seen by the other forked processes as each one gets its own copy of the memory once it writes to: there shouldn't be any automatically shared memory between all these forked processes and the main one. So, the parent will just see the one from the first C.fork().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, making it exceedingly clear that pid != pid depending on whether you're in the child process or the parent process would make things more convoluted, and what I went with was indeed not very clear, thanks ;).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May be mention that in the double_fork case, the pid we return is of no use and not valid (as it has already been wait()ed).
Dunno if we should return pid=nil, or that first fork pid anyway (so the caller can have something to wait() on, that will return the expected value of no more there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, good point.
I guess nil sounds good, because I'm not quite sure what waitpid returns for an already-reaped PID (ECHILD?).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, ECHILD it is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, just documented it, since isSubprocessDone can handle it sanely. Returning nil would potentially be less friendly ;).
There are also a few things that use it via |
pid is 0 *in the child propcess*, in the parent process, it's the child's pid. Since they both have separate memory mappings, use a single variable name.
Final RFC, I'll do a final check later today before merging ;). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No issue for me over a weekend with this (so, lua blitting only) and the frontend buddy PR.
(Unrelated: may be we could build the C libblitbuffer.so on all platforms, and have it loaded not just simply if it is present, but based on another setting |
@poire-z : That was basically the plan after this, as nightmode was essentially the only missing feature with the C blitter on eInk ;). (Remember that toggle in the Dev menu? ^^). |
Companion PR to koreader/koreader-base#884 * Basically flags devices known to be stable when using PxP inversion. * Plus, random fix for #4870 ;). * A few FrontLight tweaks & cleanups on Kobo: * Moved the Kobo-specific startup status insanity to Kobo-specific init * Made turnOff/turnOn frontlight do a smooth ramp down/up * On Kobo, use turnOff/turnOn for suspend/resume, to get that smooth toggle * On Kobo, for NaturalLight w/ a mixer, only set warmth for setWarmth, and only set Brightness for setBrightness, otherwise, it tried to set both with not in-sync values, which made the FL widget jittery.
Companion PR to koreader/koreader-base#884 * Basically flags devices known to be stable when using PxP inversion. * Plus, random fix for koreader#4870 ;). * A few FrontLight tweaks & cleanups on Kobo: * Moved the Kobo-specific startup status insanity to Kobo-specific init * Made turnOff/turnOn frontlight do a smooth ramp down/up * On Kobo, use turnOff/turnOn for suspend/resume, to get that smooth toggle * On Kobo, for NaturalLight w/ a mixer, only set warmth for setWarmth, and only set Brightness for setBrightness, otherwise, it tried to set both with not in-sync values, which made the FL widget jittery.
Crappy puns aside: