blob: 6fbfff56bf6760f4d5782f2c809d8bf3f0c11962 [file] [log] [blame]
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build windows
// +build windows
package windriver
import (
"errors"
"image"
"image/color"
"image/draw"
"sync"
"syscall"
"unsafe"
"golang.org/x/exp/shiny/driver/internal/win32"
"golang.org/x/exp/shiny/screen"
)
type textureImpl struct {
size image.Point
dc syscall.Handle
bitmap syscall.Handle
mu sync.Mutex
released bool
}
type handleCreateTextureParams struct {
size image.Point
dc syscall.Handle
bitmap syscall.Handle
err error
}
var msgCreateTexture = win32.AddScreenMsg(handleCreateTexture)
func newTexture(size image.Point) (screen.Texture, error) {
p := handleCreateTextureParams{size: size}
win32.SendScreenMessage(msgCreateTexture, 0, uintptr(unsafe.Pointer(&p)))
if p.err != nil {
return nil, p.err
}
return &textureImpl{
size: size,
dc: p.dc,
bitmap: p.bitmap,
}, nil
}
func handleCreateTexture(hwnd syscall.Handle, uMsg uint32, wParam, lParam uintptr) {
// This code needs to run on Windows message pump thread.
// Firstly, it calls GetDC(nil) and, according to Windows documentation
// (https://msdn.microsoft.com/en-us/library/windows/desktop/dd144871(v=vs.85).aspx),
// has to be released on the same thread.
// Secondly, according to Windows documentation
// (https://msdn.microsoft.com/en-us/library/windows/desktop/dd183489(v=vs.85).aspx),
// ... thread that calls CreateCompatibleDC owns the HDC that is created.
// When this thread is destroyed, the HDC is no longer valid. ...
// So making Windows message pump thread own returned HDC makes DC
// live as long as we want to.
p := (*handleCreateTextureParams)(unsafe.Pointer(lParam))
screenDC, err := win32.GetDC(0)
if err != nil {
p.err = err
return
}
defer win32.ReleaseDC(0, screenDC)
dc, err := _CreateCompatibleDC(screenDC)
if err != nil {
p.err = err
return
}
bitmap, err := _CreateCompatibleBitmap(screenDC, int32(p.size.X), int32(p.size.Y))
if err != nil {
_DeleteDC(dc)
p.err = err
return
}
p.dc = dc
p.bitmap = bitmap
}
func (t *textureImpl) Bounds() image.Rectangle {
return image.Rectangle{Max: t.size}
}
func (t *textureImpl) Fill(r image.Rectangle, c color.Color, op draw.Op) {
err := t.update(func(dc syscall.Handle) error {
return fill(dc, r, c, op)
})
if err != nil {
panic(err) // TODO handle error
}
}
func (t *textureImpl) Release() {
if err := t.release(); err != nil {
panic(err) // TODO handle error
}
}
func (t *textureImpl) release() error {
t.mu.Lock()
defer t.mu.Unlock()
if t.released {
return nil
}
t.released = true
err := _DeleteObject(t.bitmap)
if err != nil {
return err
}
return _DeleteDC(t.dc)
}
func (t *textureImpl) Size() image.Point {
return t.size
}
func (t *textureImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle) {
err := t.update(func(dc syscall.Handle) error {
return src.(*bufferImpl).blitToDC(dc, dp, sr)
})
if err != nil {
panic(err) // TODO handle error
}
}
// update prepares texture t for update and executes f over texture device
// context dc in a safe manner.
func (t *textureImpl) update(f func(dc syscall.Handle) error) (retErr error) {
t.mu.Lock()
defer t.mu.Unlock()
if t.released {
return errors.New("windriver: Texture.Upload called after Texture.Release")
}
// Select t.bitmap into t.dc, so our drawing gets recorded
// into t.bitmap and not into 1x1 default bitmap created
// during CreateCompatibleDC call.
prev, err := _SelectObject(t.dc, t.bitmap)
if err != nil {
return err
}
defer func() {
_, err2 := _SelectObject(t.dc, prev)
if retErr == nil {
retErr = err2
}
}()
return f(t.dc)
}