-
Notifications
You must be signed in to change notification settings - Fork 106
/
framebuffer.lua
584 lines (507 loc) · 19.2 KB
/
framebuffer.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
--[[--
Framebuffer API.
This will be extended by implementations of this API.
@module ffi.framebuffer
--]]
local Blitbuffer = require("ffi/blitbuffer")
local fb = {
device = nil, -- points to a device object
debug = function(...) --[[ NOP ]] end,
bb = nil, -- should be set by implementations
full_bb = nil, -- will hold a reference to the full-size native buffer when a viewport is set
viewport = nil,
screen_size = nil,
native_rotation_mode = nil,
cur_rotation_mode = nil,
blitbuffer_rotation_mode = nil,
night_mode = false,
hw_dithering = false, -- will be setup via setupDithering @ startup by reader.lua
sw_dithering = false, -- will be setup via setupDithering @ startup by reader.lua
swipe_animations = false, -- will be toggled at page turn by the frontend
is_always_portrait = false, -- true = SW flip landscape into portrait (historically a bit of misnomer)
forced_rotation = nil, --[[{
-- canonically the order below - but frontend may specify their own mapping
fb.DEVICE_ROTATED_UPRIGHT,
fb.DEVICE_ROTATED_CLOCKWISE,
fb.DEVICE_ROTATED_UPSIDE_DOWN,
fb.DEVICE_ROTATED_COUNTER_CLOCKWISE,
restore = false, -- true if hw rot mode is to be restored after painting, see framebuffer_linux:afterPaint()
default = nil, -- if not nil, use this as default rotation value (this one is canonical, not HW)
every_paint = false, -- true if HW mode should be enforced for every paint batch
},]]
-- User-selectable WF aggressiveness.
-- Lower level is the slowest and most conservative, increasing values trade speed for glitches and ghosting.
wf_level = 0,
wf_level_max = 0, -- Maximum supported value for wf_level.
-- Whether the user (or a Device cap check) chose to disable MXCFB_WAIT_FOR_UPDATE_* ioctls.
mxcfb_bypass_wait_for = false,
-- Whether the user chose to disable CFA post-processing
no_cfa_post_processing = false,
}
--[[
Canonical values for rotation modes:
0 for no rotation (i.e., Portrait),
1 for landscape with bottom on the right side of screen, etc.
2
+--------------+
| +----------+ |
| | | |
| | Freedom! | |
| | | |
| | | |
3 | | | | 1
| | | |
| | | |
| +----------+ |
| |
| |
+--------------+
0
i.e., this matches <linux/fb.h> FB_ROTATE_* constants ;).
This corresponds to the user-facing *physical device* rotation (in 90° CW steps).
NOTE: The fact that we tend to make strong assumptions about UPRIGHT being a Portrait orientation
is simply based on the fact that it currently matches the form-factor of our target platforms.
But if you were to take a widescreen TV/monitor, its UPRIGHT orientation would be a Landscape one...
--]]
fb.DEVICE_ROTATED_UPRIGHT = 0 -- UR, Portait
fb.DEVICE_ROTATED_CLOCKWISE = 1 -- CW, Landscape
fb.DEVICE_ROTATED_UPSIDE_DOWN = 2 -- UD, Inverted Portrait
fb.DEVICE_ROTATED_COUNTER_CLOCKWISE = 3 -- CCW, Inverted Landscape
--[[
Rotation modes are not guaranteed to be canonical however, as HW driver may interpret the value as CW or CCW, as well
as starting from elsewhere than portrait so in practice the values can be pretty much anything.
For such an occasion the frontend can provide custom mappings, or simply override the HW setter/getter/map function.
]]
-- Invert HW value to canonical one, needed to interpret the one given by OS.
-- Some devices may wish to override this if the mapping is not as simple as 1:1.
function fb:getCanonicalRotationMode(hw)
for k,v in ipairs(self.forced_rotation) do
if v == hw then
return k-1
end
end
end
-- To be overridden. Returns the current FB rotation exactly as reported by hw
-- Pipe the result into getCanonicalRotationMode() if you want canonical (ie the koreader uses) result.
function fb:getHWRotation()
return 0
end
-- To be overridden. Set HW rotation of the FB. Note that the argument is canonical value, ie this
-- call should do translation from forced_rotation[] table - or any other necessary dance to convert
-- canonical fb.DEVICE_ROTATED_ mode to HW reality.
function fb:setHWRotation(canon)
end
-- To be overriden. Enable nightmode globally by switching the grayscale fb vinfo flag.
-- Requires the canHWInvert device cap.
function fb:setHWNightmode(toggle)
end
-- To be overriden. Returns true if the grayscale flag is set to GRAYSCALE_8BIT_INVERTED on an 8bpp fb.
function fb:getHWNightmode()
return false
end
-- Rotation modes may cause desync of rotation for touch translation, so frontend may need to override this
-- to provide a fixup.
function fb:getTouchRotation()
-- The touch matrix stays the same for things like linuxfb so just output the rotation we're doing either hw or sw.
-- But this may not be necessarily so with other backends.
return self.cur_rotation_mode
end
local EMULATE_READER_DPI = tonumber(os.getenv("EMULATE_READER_DPI"))
function fb:extend(o)
o = o or {}
setmetatable(o, self)
self.__index = self
o.parent = self
return o
end
function fb:new(o)
o = self:extend(o)
if o.init then o:init() end
return o
end
-- you can do the setup here, but remember to call this from implementations!
function fb:init()
self.blitbuffer_rotation_mode = self.bb:getRotation()
-- asking the framebuffer for orientation is error prone,
-- so we do this simple heuristic (for now)
self.screen_size = self:getRawSize()
if self.screen_size.w > self.screen_size.h and self.is_always_portrait then
self.screen_size.w, self.screen_size.h = self.screen_size.h, self.screen_size.w
-- some framebuffers need to be rotated counter-clockwise (they start in landscape mode)
io.write("FB: Enforcing portrait mode by doing an initial BB rotation\n")
self.debug("FB: This prevents the use of blitting optimizations. This should instead be fixed on the device's side on startup.")
self.bb:rotate(-90)
self.blitbuffer_rotation_mode = self.bb:getRotation()
assert(not self.forced_rotation, "If forced HW rotation is used, isAlwaysPortrait should not be set.")
end
self.native_rotation_mode = self.forced_rotation and self.forced_rotation.default or self.DEVICE_ROTATED_UPRIGHT
self.cur_rotation_mode = self.native_rotation_mode
end
-- This method must be called just before drawing a sequence of blits into a framebuffer.
-- It's ok to spam it for each paint, drivers should ensure to become nop for subsequent calls until final afterPaint().
function fb:beforePaint()
end
-- This method must be called once we're done drawing all batched updates into a framebuffer, and *after* all the necessary fb:refresh* calls.
function fb:afterPaint()
end
-- the ...Imp methods may be overridden to implement refresh
function fb:refreshFullImp(x, y, w, h, d)
-- the simplest form of refresh implements only this method.
-- the others default to fall back to this.
end
function fb:refreshPartialImp(x, y, w, h, d)
return self:refreshFullImp(x, y, w, h, d)
end
function fb:refreshNoMergePartialImp(x, y, w, h, d)
return self:refreshPartialImp(x, y, w, h, d)
end
function fb:refreshFlashPartialImp(x, y, w, h, d)
return self:refreshFullImp(x, y, w, h, d)
end
function fb:refreshUIImp(x, y, w, h, d)
return self:refreshPartialImp(x, y, w, h, d)
end
function fb:refreshNoMergeUIImp(x, y, w, h, d)
return self:refreshUIImp(x, y, w, h, d)
end
function fb:refreshFlashUIImp(x, y, w, h, d)
return self:refreshFullImp(x, y, w, h, d)
end
function fb:refreshFastImp(x, y, w, h, d)
return self:refreshPartialImp(x, y, w, h, d)
end
function fb:refreshA2Imp(x, y, w, h, d)
return self:refreshFastImp(x, y, w, h, d)
end
function fb:refreshWaitForLastImp()
-- default is NOP
end
-- these should not be overridden, they provide the external refresh API:
--- @note: x, y, w, h are *mandatory*, even for refreshFull! (UIManager guarantees it).
function fb:refreshFull(x, y, w, h, d)
x, y, w, h = self:calculateRealCoordinates(x, y, w, h)
return self:refreshFullImp(x, y, w, h, d)
end
function fb:refreshPartial(x, y, w, h, d)
x, y, w, h = self:calculateRealCoordinates(x, y, w, h)
return self:refreshPartialImp(x, y, w, h, d)
end
function fb:refreshNoMergePartial(x, y, w, h, d)
x, y, w, h = self:calculateRealCoordinates(x, y, w, h)
return self:refreshNoMergePartialImp(x, y, w, h, d)
end
function fb:refreshFlashPartial(x, y, w, h, d)
x, y, w, h = self:calculateRealCoordinates(x, y, w, h)
return self:refreshFlashPartialImp(x, y, w, h, d)
end
function fb:refreshUI(x, y, w, h, d)
x, y, w, h = self:calculateRealCoordinates(x, y, w, h)
return self:refreshUIImp(x, y, w, h, d)
end
function fb:refreshNoMergeUI(x, y, w, h, d)
x, y, w, h = self:calculateRealCoordinates(x, y, w, h)
return self:refreshNoMergeUIImp(x, y, w, h, d)
end
function fb:refreshFlashUI(x, y, w, h, d)
x, y, w, h = self:calculateRealCoordinates(x, y, w, h)
return self:refreshFlashUIImp(x, y, w, h, d)
end
function fb:refreshFast(x, y, w, h, d)
x, y, w, h = self:calculateRealCoordinates(x, y, w, h)
return self:refreshFastImp(x, y, w, h, d)
end
function fb:refreshA2(x, y, w, h, d)
x, y, w, h = self:calculateRealCoordinates(x, y, w, h)
return self:refreshA2Imp(x, y, w, h, d)
end
function fb:refreshWaitForLast()
return self:refreshWaitForLastImp()
end
-- should be overridden to free resources
function fb:close()
end
-- you probably do not need to override any of the following functions:
--[[
set a rectangle that represents the area of the screen we are working on
--]]
function fb:setViewport(viewport)
if viewport.x < 0 or viewport.x > self.screen_size.w
or viewport.y < 0 or viewport.y > self.screen_size.h
or viewport.w < 0 or viewport.x+viewport.w > self.screen_size.w
or viewport.h < 0 or viewport.y+viewport.h > self.screen_size.h
then
error("fb:setViewport() bad viewport")
end
-- we might be switching the viewport on the fly, so don't overwrite self.full_bb
if not self.full_bb then
self.full_bb = self.bb
end
self.debug("fb:setViewport() setting viewport to",
viewport.x, viewport.y,
viewport.w, viewport.h)
self.bb = self.full_bb:viewport(
viewport.x, viewport.y,
viewport.w, viewport.h)
self.viewport = viewport
self.full_bb:fill(Blitbuffer.COLOR_WHITE)
-- We want the *viewport's* dimensions, as calculateRealCoordinates will adjust the coordinates later.
self:refreshFull(0, 0, self:getWidth(), self:getHeight())
end
-- If there is a viewport in place, spit out adjusted coordinates for the native buffer that account for it
function fb:calculateRealCoordinates(x, y, w, h)
if not self.viewport then return x, y, w, h end
-- TODO: May need to implement refresh translations for HW rotate on broken drivers.
-- For now those should just avoid using HW mode altogether.
--[[
we need to adapt the coordinates when we have a viewport.
this adaptation depends on the rotation:
0,0 fb.w
+---+---------------------------+---+
| |v.y v.y| |
|v.x| |vx2|
+---+---------------------------+---+
| | v.w | |
| | | |
| | | |
| |v.h (viewport) | |
| | | | fb.h
| | | |
| | | |
| | | |
+---+---------------------------+---+
|v.x| |vx2|
| |vy2 vy2| |
+---+---------------------------+---+
The viewport offset v.y/v.x only applies when rotation is 0 degrees.
For other rotations (0,0 is in one of the other edges), we need to
recalculate the offsets.
--]]
-- Ensure OOB coordinates do not allow requesting a refresh large enough to spill *outside* of the viewport...
-- NOTE: This is done here, instead of in the public refresh*() methods (where we'd explicitly run it on the viewport bb),
-- because implementations may prefer to bound to the *full* screen, instead of the viewport's dimensions,
-- especially if an alignment constraint is at play...
x, y, w, h = self.bb:getBoundedRect(x, y, w, h)
local mode = self:getRotationMode()
local vx2 = self.screen_size.w - (self.viewport.x + self.viewport.w)
local vy2 = self.screen_size.h - (self.viewport.y + self.viewport.h)
if mode == self.DEVICE_ROTATED_UPRIGHT then
-- (0, 0) is at top left of screen
x = x + self.viewport.x
y = y + self.viewport.y
elseif mode == self.DEVICE_ROTATED_CLOCKWISE then
-- (0, 0) is at bottom left of screen
x = x + vy2
y = y + self.viewport.x
elseif mode == self.DEVICE_ROTATED_UPSIDE_DOWN then
-- (0, 0) is at bottom right of screen
x = x + vx2
y = y + vy2
else -- self.DEVICE_ROTATED_COUNTER_CLOCKWISE
-- (0, 0) is at top right of screen
x = x + self.viewport.y
y = y + vx2
end
return x, y, w, h
end
function fb:getRawSize()
return {w = self.bb:getWidth(), h = self.bb:getHeight()}
end
function fb:getWidth()
return self.bb:getWidth()
end
function fb:getHeight()
return self.bb:getHeight()
end
function fb:getScreenWidth()
return self.screen_size.w
end
function fb:getScreenHeight()
return self.screen_size.h
end
function fb:getDPI()
if self.dpi ~= nil then
return self.dpi
end
self.dpi = EMULATE_READER_DPI
if self.dpi == nil and self.device then
self.dpi = self.device.display_dpi
end
if self.dpi == nil then
self.dpi = 160
end
return self.dpi
end
function fb:clearDPI()
self.dpi = nil
-- Restore self.dpi to defaults
self:getDPI()
-- Clear the override flag
self.dpi_override = nil
end
function fb:setDPI(dpi)
-- If we passed a nil, reset to defaults and clear the override flag
if not dpi then
return self:clearDPI()
end
-- Nothing except having a real DPI override ("screen_dpi" reader setting non nil) set calls Device:setScreenDPI -> fb:setDPI
self.dpi = dpi
-- Remember that we're using an override for scaleBySize
self.dpi_override = true
end
--[[--
Calculate pixel from density-independent pixel
@int dp density-independent pixel
@treturn int pixel
]]--
function fb:scaleByDPI(dp)
-- Round up, to make sure we never end up with a zero
return math.ceil(dp * self:getDPI() / 160)
end
function fb:scaleBySize(px)
-- Larger screen needs larger scale
local size_scale = math.min(self:getWidth(), self:getHeight()) / 600
-- Unless the user has a DPI override set, DPI doesn't actually factor into the equation at all.
local dpi_scale = size_scale
if self.dpi_override then
dpi_scale = self.dpi / 160
end
-- Round up, to make sure we never end up with a zero
return math.ceil(px * (size_scale + dpi_scale) / 2)
end
function fb:getRotationMode()
return self.cur_rotation_mode
end
--- This reflects how the screen *looks* like, not the screen layout relative to its native orientation...
function fb:getScreenMode()
if self:getWidth() > self:getHeight() then
return "landscape"
else
return "portrait"
end
end
--- This reflects the current layout in terms of *rotation* modes
function fb:getScreenOrientation()
if bit.band(self:getRotationMode(), 1) == 1 then
-- LinuxFB constants, Landscapes are odds
return "landscape"
else
-- And Portraits are even
return "portrait"
end
end
-- Configure desired rotation. By default, we setup the blitter to do rotations for us, but a subclass
-- may implement rotation via hardware (android with hasNativeRotation, linuxfb with forced_rotation).
function fb:setRotationMode(mode)
-- This, on the other hand, is responsible for the internal *buffer* rotation,
-- as such, it's inverted compared to the DEVICE_ROTATED_ constants; i.e., it's in 90° CCW steps).
self.bb:rotateAbsolute(-90 * (mode - self.native_rotation_mode - self.blitbuffer_rotation_mode))
if self.viewport then
self.full_bb:setRotation(self.bb:getRotation())
end
self.cur_rotation_mode = mode
end
function fb:getWindowTitle()
return self.window_title
end
function fb:setWindowTitle(new_title)
if new_title and new_title ~= "" then
self.window_title = new_title .. " - KOReader"
else
self.window_title = "KOReader"
end
end
function fb:toggleNightMode()
self.night_mode = not self.night_mode
if self.device:canHWInvert() then
-- If the device supports global inversion via the grayscale flag, do that.
self:setHWNightmode(self.night_mode)
else
-- Only do SW inversion if the HW can't...
self.bb:invert()
if self.viewport then
-- invert and blank out the full framebuffer when we are working on a viewport
self.full_bb:setInverse(self.bb:getInverse())
self.full_bb:fill(Blitbuffer.COLOR_WHITE)
end
end
end
function fb:toggleHWDithering(toggle)
if toggle == true then
self.hw_dithering = true
elseif toggle == false then
self.hw_dithering = false
else
self.hw_dithering = not self.hw_dithering
end
end
function fb:toggleSWDithering(toggle)
if toggle == true then
self.sw_dithering = true
elseif toggle == false then
self.sw_dithering = false
else
self.sw_dithering = not self.sw_dithering
end
end
function fb:setupDithering()
-- Prefer HW dither to SW dither
if self.device:canHWDither() then
self.hw_dithering = true
else
self.hw_dithering = false
end
if self.hw_dithering then
self.sw_dithering = false
else
-- We only handle SW dithering @ 8bpp, to keep things simple
if self.device.screen.fb_bpp == 8 then
self.sw_dithering = true
else
self.sw_dithering = false
end
end
end
-- To be overriden.
function fb:setSwipeAnimations(enabled)
end
-- To be overriden.
function fb:setSwipeDirection(direction)
end
function fb:getWaveformLevel()
return math.min(self.wf_level_max, self.wf_level)
end
function fb:getMxcWaitForBypass()
return self.mxcfb_bypass_wait_for
end
function fb:noCFAPostProcess()
return self.no_cfa_post_processing
end
function fb:saveCurrentBB()
if self.saved_bb then self.saved_bb:free() end
self.saved_bb = self.bb:copy()
end
function fb:restoreFromSavedBB()
if self.saved_bb then
self.bb:blitFullFrom(self.saved_bb)
-- free data
self.saved_bb:free()
self.saved_bb = nil
end
end
function fb:shot(filename)
-- Handle the RGB->BGR switcheroo if need be
local bgr = false
if self.device:hasBGRFrameBuffer() then
bgr = true
end
self.bb:writePNG(filename, bgr)
end
-- Clear the screen to white
function fb:clear()
if self.viewport then
self.full_bb:fill(Blitbuffer.COLOR_WHITE)
else
self.bb:fill(Blitbuffer.COLOR_WHITE)
end
end
return fb